From bf1bde5d6a8306284a0cce89eb8f492b8c9b7a67 Mon Sep 17 00:00:00 2001 From: Dachi Natsvlishvili Date: Fri, 17 Aug 2018 20:00:01 +0400 Subject: [PATCH 1/7] Add Georgian language translation (#8250) * Add Georgian language translation * i18n-tasks normalize --- app/helpers/settings_helper.rb | 1 + app/javascript/mastodon/locales/ka.json | 311 ++++++ .../mastodon/locales/whitelist_ka.json | 2 + config/application.rb | 1 + config/locales/activerecord.ka.yml | 13 + config/locales/devise.ka.yml | 82 ++ config/locales/doorkeeper.ka.yml | 142 +++ config/locales/ka.yml | 893 ++++++++++++++++++ config/locales/simple_form.ka.yml | 99 ++ 9 files changed, 1544 insertions(+) create mode 100644 app/javascript/mastodon/locales/ka.json create mode 100644 app/javascript/mastodon/locales/whitelist_ka.json create mode 100644 config/locales/activerecord.ka.yml create mode 100644 config/locales/devise.ka.yml create mode 100644 config/locales/doorkeeper.ka.yml create mode 100644 config/locales/ka.yml create mode 100644 config/locales/simple_form.ka.yml diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 8528be267..14ca2333e 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -26,6 +26,7 @@ module SettingsHelper io: 'Ido', it: 'Italiano', ja: '日本語', + ka: 'ქართული', ko: '한국어', nl: 'Nederlands', no: 'Norsk', diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json new file mode 100644 index 000000000..494270b9d --- /dev/null +++ b/app/javascript/mastodon/locales/ka.json @@ -0,0 +1,311 @@ +{ + "account.badges.bot": "ბოტი", + "account.block": "დაბლოკე @{name}", + "account.block_domain": "დაიმალოს ყველაფერი დომენიდან {domain}", + "account.blocked": "დაიბლოკა", + "account.direct": "პირდაპირი წერილი @{name}-ს", + "account.disclaimer_full": "ქვემოთ მოცემულმა ინფორმაციამ შეიძლება სრულად არ ასახოს მომხმარებლის პროფილი.", + "account.domain_blocked": "დომენი დამალულია", + "account.edit_profile": "პროფილის ცვლილება", + "account.endorse": "გამორჩევა პროფილზე", + "account.follow": "გაყოლა", + "account.followers": "მიმდევრები", + "account.follows": "მიდევნებები", + "account.follows_you": "მოგყვებათ", + "account.hide_reblogs": "დაიმალოს ბუსტები @{name}-სგან", + "account.media": "მედია", + "account.mention": "ასახელეთ @{name}", + "account.moved_to": "{name} გადავიდა:", + "account.mute": "გააჩუმე @{name}", + "account.mute_notifications": "გააჩუმე შეტყობინებები @{name}-სგან", + "account.muted": "გაჩუმებული", + "account.posts": "ტუტები", + "account.posts_with_replies": "ტუტები და პასუხები", + "account.report": "დაარეპორტე @{name}", + "account.requested": "დამტკიცების მოლოდინში. დააწკაპუნეთ რომ უარყოთ დადევნების მოთხონვა", + "account.share": "გააზიარე @{name}-ის პროფილი", + "account.show_reblogs": "აჩვენე ბუსტები @{name}-სგან", + "account.unblock": "განბლოკე @{name}", + "account.unblock_domain": "გამოაჩინე {domain}", + "account.unendorse": "არ გამოირჩეს პროფილზე", + "account.unfollow": "ნუღარ მიჰყვები", + "account.unmute": "ნუღარ აჩუმებ @{name}-ს", + "account.unmute_notifications": "ნუღარ აჩუმებ შეტყობინებებს @{name}-სგან", + "account.view_full_profile": "სრული პროფილის ჩვენება", + "alert.unexpected.message": "წარმოიშვა მოულოდნელი შეცდომა.", + "alert.unexpected.title": "უპს!", + "boost_modal.combo": "შეგიძლიათ დააჭიროთ {combo}-ს რათა შემდეგ ჯერზე გამოტოვოთ ეს", + "bundle_column_error.body": "ამ კომპონენტის ჩატვირთვისას რაღაც აირია.", + "bundle_column_error.retry": "სცადეთ კიდევ ერთხელ", + "bundle_column_error.title": "ქსელის შეცდომა", + "bundle_modal_error.close": "დახურვა", + "bundle_modal_error.message": "ამ კომპონენტის ჩატვირთვისას რაღაც აირია.", + "bundle_modal_error.retry": "სცადეთ კიდევ ერთხელ", + "column.blocks": "დაბლოკილი მომხმარებლები", + "column.community": "ლოკალური თაიმლაინი", + "column.direct": "პირდაპირი წერილები", + "column.domain_blocks": "დამალული დომენები", + "column.favourites": "ფავორიტები", + "column.follow_requests": "დადევნების მოთხოვნები", + "column.home": "სახლი", + "column.lists": "სიები", + "column.mutes": "გაჩუმებული მომხმარებლები", + "column.notifications": "შეტყობინებები", + "column.pins": "აპინული ტუტები", + "column.public": "ფედერალური თაიმლაინი", + "column_back_button.label": "უკან", + "column_header.hide_settings": "პარამეტრების დამალვა", + "column_header.moveLeft_settings": "სვეტის მარცხნივ გადატანა", + "column_header.moveRight_settings": "სვეტის მარჯვნივ გადატანა", + "column_header.pin": "აპინვა", + "column_header.show_settings": "პარამეტრების ჩვენება", + "column_header.unpin": "პინის მოხსნა", + "column_subheading.settings": "პარამეტრები", + "community.column_settings.media_only": "მხოლოდ მედია", + "compose_form.direct_message_warning": "ეს ტუტი გაეგზავნება მხოლოდ ნახსენებ მომხმარებლებს.", + "compose_form.direct_message_warning_learn_more": "გაიგე მეტი", + "compose_form.hashtag_warning": "ეს ტუტი არ მოექცევა ჰეშტეგების ქვეს, რამეთუ ის არაა მითითებული. მხოლოდ ღია ტუტები მოიძებნება ჰეშტეგით.", + "compose_form.lock_disclaimer": "თქვენი ანგარიში არაა {locked}. ნებისმიერს შეიძლია გამოგყვეთ, რომ იხილოს თქვენი მიმდევრებზე გათვლილი პოსტები.", + "compose_form.lock_disclaimer.lock": "ჩაკეტილი", + "compose_form.placeholder": "რაზე ფიქრობ?", + "compose_form.publish": "ტუტი", + "compose_form.publish_loud": "{publish}!", + "compose_form.sensitive.marked": "მედია მონიშნულია მგრძნობიარედ", + "compose_form.sensitive.unmarked": "მედია არაა მონიშნული მგრძნობიარედ", + "compose_form.spoiler.marked": "გაფრთხილების უკან ტექსტი დამალულია", + "compose_form.spoiler.unmarked": "ტექსტი არაა დამალული", + "compose_form.spoiler_placeholder": "თქვენი გაფრთხილება დაწერეთ აქ", + "confirmation_modal.cancel": "უარყოფა", + "confirmations.block.confirm": "ბლოკი", + "confirmations.block.message": "დარწმუნებული ხართ, გსურთ დაბლოკოთ {name}?", + "confirmations.delete.confirm": "გაუქმება", + "confirmations.delete.message": "დარწმუნებული ხართ, გსურთ გააუქმოთ ეს სტატუსი?", + "confirmations.delete_list.confirm": "გაუქმება", + "confirmations.delete_list.message": "დარწმუნებული ხართ, გსურთ სამუდამოდ გააუქმოთ ეს სია?", + "confirmations.domain_block.confirm": "მთელი დომენის დამალვა", + "confirmations.domain_block.message": "ნაღდად, ნაღდად, დარწმუნებული ხართ, გსურთ დაბლოკოთ მთელი {domain}? უმეტეს შემთხვევაში რამდენიმე გამიზნული ბლოკი ან გაჩუმება საკმარისი და უკეთესია. კონტენტს ამ დომენიდან ვერ იხილავთ ვერც ერთ ღია თაიმლაინზე ან თქვენს შეტყობინებებში. ამ დომენიდან არსებული მიმდევრები ამოიშლება.", + "confirmations.mute.confirm": "გაჩუმება", + "confirmations.mute.message": "დარწმუნებული ხართ, გსურთ გააჩუმოთ {name}?", + "confirmations.redraft.confirm": "გაუქმება და გადანაწილება", + "confirmations.redraft.message": "დარწმუნებული ხართ, გსურთ გააუქმოთ ეს სტატუსი და გადაანაწილოთ? დაკარგავთ ყველა პასუხს, ბუსტს და მასზედ არსებულ ფავორიტს.", + "confirmations.unfollow.confirm": "ნუღარ მიჰყვები", + "confirmations.unfollow.message": "დარწმუნებული ხართ, აღარ გსურთ მიჰყვებოდეთ {name}-ს?", + "embed.instructions": "ეს სტატუსი ჩასვით თქვენს ვებ-საიტზე შემდეგი კოდის კოპირებით.", + "embed.preview": "ესაა თუ როგორც გამოჩნდება:", + "emoji_button.activity": "აქტივობა", + "emoji_button.custom": "პერსონალიზირებული", + "emoji_button.flags": "დროშები", + "emoji_button.food": "საჭმელი და სასლმელი", + "emoji_button.label": "ემოჯის ჩასმა", + "emoji_button.nature": "ბუმება", + "emoji_button.not_found": "არაა ემოჯი!! (╯°□°)╯︵ ┻━┻", + "emoji_button.objects": "ობიექტები", + "emoji_button.people": "ხალხი", + "emoji_button.recent": "ხშირად გამოყენებული", + "emoji_button.search": "ძებნა...", + "emoji_button.search_results": "ძებნის შედეგები", + "emoji_button.symbols": "სიმბოლოები", + "emoji_button.travel": "მოგზაურობა და ადგილები", + "empty_column.community": "ლოკალური თაიმლაინი ცარიელია. დაწერეთ რაიმე ღიად ან ქენით რაიმე სხვა!", + "empty_column.direct": "ჯერ პირდაპირი წერილები არ გაქვთ. როდესაც მიიღებთ ან გააგზავნით, გამოჩნდება აქ.", + "empty_column.hashtag": "ამ ჰეშტეგში ჯერ არაფერია.", + "empty_column.home": "თქვენი სახლის თაიმლაინი ცარიელია! ესტუმრეთ {public}-ს ან დასაწყისისთვის გამოიყენეთ ძებნა, რომ შეხვდეთ სხვა მომხმარებლებს.", + "empty_column.home.public_timeline": "ღია თაიმლაინი", + "empty_column.list": "ამ სიაში ჯერ არაფერია. როდესაც სიის წევრები დაპოსტავენ ახალ სტატუსებს, ისინი გამოჩნდებიან აქ.", + "empty_column.notifications": "ჯერ შეტყობინებები არ გაქვთ. საუბრის დასაწყებად იურთიერთქმედეთ სხვებთან.", + "empty_column.public": "აქ არაფერია! შესავსებად, დაწერეთ რაიმე ღიად ან ხელით გაჰყევით მომხმარებლებს სხვა ინსტანციებისგან", + "follow_request.authorize": "ავტორიზაცია", + "follow_request.reject": "უარყოფა", + "getting_started.developers": "დეველოპერები", + "getting_started.documentation": "დოკუმენტაცია", + "getting_started.find_friends": "იპოვეთ მეგობრები ტვიტერიდან", + "getting_started.heading": "დაწყება", + "getting_started.invite": "ხალხის მოწვევა", + "getting_started.open_source_notice": "მასტოდონი ღია პროგრამაა. შეგიძლიათ შეუწყოთ ხელი ან შექმნათ პრობემის რეპორტი {github}-ზე.", + "getting_started.security": "უსაფრთხოება", + "getting_started.terms": "მომსახურების პირობები", + "home.column_settings.basic": "ძირითადი", + "home.column_settings.show_reblogs": "ბუსტების ჩვენება", + "home.column_settings.show_replies": "პასუხების ჩვენება", + "keyboard_shortcuts.back": "უკან გადასასვლელად", + "keyboard_shortcuts.boost": "დასაბუსტად", + "keyboard_shortcuts.column": "ერთ-ერთი სვეტში სტატუსზე ფოკუსირებისთვის", + "keyboard_shortcuts.compose": "შედგენის ტექსტ-არეაზე ფოკუსირებისთვის", + "keyboard_shortcuts.description": "აღწერილობა", + "keyboard_shortcuts.down": "სიაში ქვემოთ გადასაადგილებლად", + "keyboard_shortcuts.enter": "სტატუსის გასახსნელად", + "keyboard_shortcuts.favourite": "ფავორიტად ქცევისთვის", + "keyboard_shortcuts.heading": "კლავიატურის სწრაფი ბმულები", + "keyboard_shortcuts.hotkey": "ცხელი კლავიში", + "keyboard_shortcuts.legend": "ამ ლეგენდის გამოსაჩენად", + "keyboard_shortcuts.mention": "ავტორის დასახელებლად", + "keyboard_shortcuts.profile": "ავტორის პროფილის გასახსნელად", + "keyboard_shortcuts.reply": "პასუხისთვის", + "keyboard_shortcuts.search": "ძიებაზე ფოკუსირებისთვის", + "keyboard_shortcuts.toggle_hidden": "გაფრთხილების უკან ტექსტის გამოსაჩენად/დასამალვად", + "keyboard_shortcuts.toot": "ახალი ტუტის დასაწყებად", + "keyboard_shortcuts.unfocus": "შედგენის ტექსტ-არეაზე ფოკუსის მოსაშორებლად", + "keyboard_shortcuts.up": "სიაში ზემოთ გადასაადგილებლად", + "lightbox.close": "დახურვა", + "lightbox.next": "შემდეგი", + "lightbox.previous": "წინა", + "lists.account.add": "სიაში დამატება", + "lists.account.remove": "სიიდან ამოშლა", + "lists.delete": "სიის წაშლა", + "lists.edit": "სიის შეცვლა", + "lists.new.create": "სიის დამატება", + "lists.new.title_placeholder": "ახალი სიის სათაური", + "lists.search": "ძებნა ადამიანებს შორის რომელთაც მიჰყვებით", + "lists.subheading": "თქვენი სიები", + "loading_indicator.label": "იტვირთება...", + "media_gallery.toggle_visible": "ხილვადობის ჩართვა", + "missing_indicator.label": "არაა ნაპოვნი", + "missing_indicator.sublabel": "ამ რესურსის პოვნა ვერ მოხერხდა", + "mute_modal.hide_notifications": "დავმალოთ შეტყობინებები ამ მომხმარებლისგან?", + "navigation_bar.blocks": "დაბლოკილი მომხმარებლები", + "navigation_bar.community_timeline": "ლოკალური თაიმლაინი", + "navigation_bar.direct": "პირდაპირი წერილები", + "navigation_bar.discover": "აღმოაჩინე", + "navigation_bar.domain_blocks": "დამალული დომენები", + "navigation_bar.edit_profile": "შეცვალე პროფილი", + "navigation_bar.favourites": "ფავორიტები", + "navigation_bar.filters": "გაჩუმებული სიტყვები", + "navigation_bar.follow_requests": "დადევნების მოთხოვნები", + "navigation_bar.info": "ამ ინსტანციის შესახებ", + "navigation_bar.keyboard_shortcuts": "ცხელი კლავიშები", + "navigation_bar.lists": "სიები", + "navigation_bar.logout": "გასვლა", + "navigation_bar.mutes": "გაჩუმებული მომხმარებლები", + "navigation_bar.personal": "პირადი", + "navigation_bar.pins": "აპინული ტუტები", + "navigation_bar.preferences": "პრეფერენსიები", + "navigation_bar.public_timeline": "ფედერალური თაიმლაინი", + "navigation_bar.security": "უსაფრთხოება", + "notification.favourite": "{name}-მა თქვენი სტატუსი აქცია ფავორიტად", + "notification.follow": "{name} გამოგყვათ", + "notification.mention": "{name}-მა გასახელათ", + "notification.reblog": "{name}-მა დაბუსტა თქვენი სტატუსი", + "notifications.clear": "შეტყობინებების გასუფთავება", + "notifications.clear_confirmation": "დარწმუნებული ხართ, გსურთ სამუდამოდ წაშალოთ ყველა თქვენი შეტყობინება?", + "notifications.column_settings.alert": "დესკტოპ შეტყობინებები", + "notifications.column_settings.favourite": "ფავორიტები:", + "notifications.column_settings.follow": "ახალი მიმდევრები:", + "notifications.column_settings.mention": "ხსენებები:", + "notifications.column_settings.push": "ფუშ შეტყობინებები", + "notifications.column_settings.push_meta": "ეს მოწყობილობა", + "notifications.column_settings.reblog": "ბუსტები:", + "notifications.column_settings.show": "გამოჩნდეს სვეტში", + "notifications.column_settings.sound": "ხმის დაკვრა", + "notifications.group": "{count} შეტყობინება", + "onboarding.done": "დასასრული", + "onboarding.next": "შემდეგი", + "onboarding.page_five.public_timelines": "ლოკალური თაიმლაინი {domain}-ზე საჯარო პოსტებს აჩვენებს ყველასგან. ფედერალური თაიმლაინი {domain}-ზე აჩვენებს საჯარო პოსტებს ყველასგან ვინც მიჰყვება. ეს საჯარო თაიმლაინებია, ახალი ადამიანების აღმოჩენის კარგი გზაა.", + "onboarding.page_four.home": "სახლის თაიმლაინი აჩვენებს პოსტებს ადამიანებისგან, რომლებსაც მიჰყვებით.", + "onboarding.page_four.notifications": "შეტყობინებების სვეტი აჩვენებს სხვის ურთიერთქმედებებს თქვენთან.", + "onboarding.page_one.federation": "მასტოდონი დამოუკიდებელი სერვერების ქსელია, რომლებიც ერთიანდებიან ერთი დიდი სოციალური ქსელის შექმნისთვის. ამ სერვერებს ჩვენ ვეძახით ინსტანციებს.", + "onboarding.page_one.full_handle": "თქვენი სრული სახელური", + "onboarding.page_one.handle_hint": "ეს არის ის რასაც ეტყოდით თქვენს მეგობრებს რომ მოძიონ.", + "onboarding.page_one.welcome": "კეთილი იყოს თქვენი მასტოდონში მობრძანება!", + "onboarding.page_six.admin": "თქვენი ინსტანციის ადმინისტრატორია {admin}.", + "onboarding.page_six.almost_done": "თითქმის დასრულდა...", + "onboarding.page_six.appetoot": "ბონ აპეტუტ!", + "onboarding.page_six.apps_available": "ხელმისაწვდომია {apps} აი-ოსისთვის, ანდროიდისთვის და სხვა პლატფორმებისთვის.", + "onboarding.page_six.github": "მასტოდონი უფასო ღია პროგრამაა. შეგიძლიათ დაარეპორტოთ შეცდომები, მოითხოვოთ ფუნქციები, შეუწყოთ ხელი კოდს {github}-ზე.", + "onboarding.page_six.guidelines": "საზოგადოების სახელმძღვანელო", + "onboarding.page_six.read_guidelines": "გთხოვთ გაეცნოთ {domain}-ს {guidelines}!", + "onboarding.page_six.various_app": "მობაილ აპები", + "onboarding.page_three.profile": "შეცვალეთ თქვენი პროფილი რომ შეცვალოთ ავატარი, ბიოგრაფია და დისპლეის სახელი. იქ, ასევე იხილავთ სხვა პრეფერენსიების.", + "onboarding.page_three.search": "გამოიყენეთ ძიება რომ იპოვნოთ ადამიანები და იხილოთ ჰეშტეგები, ისეთები როგორებიცაა {illustration} და {introductions}. რომ მოძებნოთ ადამიანი ვინც არაა ამ ინსტანციაზე, გამოიყენეთ სრული სახელური.", + "onboarding.page_two.compose": "პოსტები შექმენით კომპოზიციის სვეტიდან. შეგიძლიათ ატვირთოთ სურათები, შეცვალოთ კონფიდენციალურობა და ქვემოთ მოცემული პიქტოგრამით დაამატოთ კონტენტის გაფრთხილება.", + "onboarding.skip": "გამოტოვება", + "privacy.change": "სტატუსის კონფიდენციალურობის მითითება", + "privacy.direct.long": "დაიპოსტოს მხოლოდ დასახელებულ მომხმარებლებთან", + "privacy.direct.short": "პირდაპირი", + "privacy.private.long": "დაიპოსტოს მხოლოდ მიმდევრებთან", + "privacy.private.short": "მხოლოდ-მიმდევრებისთვის", + "privacy.public.long": "დაიპოსტოს საჯარო თაიმლაინებზე", + "privacy.public.short": "საჯარო", + "privacy.unlisted.long": "არ დაიპოსტოს საჯარო თაიმლაინებზე", + "privacy.unlisted.short": "ჩამოუთვლელი", + "regeneration_indicator.label": "იტვირთება…", + "regeneration_indicator.sublabel": "თქვენი სახლის ლენტა მზადდება!", + "relative_time.days": "{number}დღ", + "relative_time.hours": "{number}სთ", + "relative_time.just_now": "ახლა", + "relative_time.minutes": "{number}წთ", + "relative_time.seconds": "{number}წმ", + "reply_indicator.cancel": "უარყოფა", + "report.forward": "ფორვარდი {target}-ს", + "report.forward_hint": "ანგარიში სხვა სერვერიდანაა. გავაგზავნოთ რეპორტის ანონიმური ასლიც?", + "report.hint": "რეპორტი გაეგზავნება თქვენი ინსტანციის მოდერატორებს. ქვემოთ შეგიძლიათ დაამატოთ მიზეზი თუ რატომ არეპორტებთ ამ ანგარიშს:", + "report.placeholder": "დამატებითი კომენტარები", + "report.submit": "დასრულება", + "report.target": "არეპორტებთ {target}", + "search.placeholder": "ძებნა", + "search_popout.search_format": "დეტალური ძებნის ფორმა", + "search_popout.tips.full_text": "მარტივი ტექსტი აბრუნებს სტატუსებს რომლებიც შექმენით, აქციეთ ფავორიტად, დაბუსტეთ, ან რაშიც ასახელეთ, ასევე ემთხვევა მომხმარებლის სახელებს, დისპლეი სახელებს, და ჰეშტეგებს.", + "search_popout.tips.hashtag": "ჰეშტეგი", + "search_popout.tips.status": "სტატუსი", + "search_popout.tips.text": "მარტივი ტექსტი აბრუნებს დამთხვეულ დისპლეი სახელებს, მომხმარებლის სახელებს და ჰეშტეგებს", + "search_popout.tips.user": "მომხმარებელი", + "search_results.accounts": "ხალხი", + "search_results.hashtags": "ჰეშტეგები", + "search_results.statuses": "ტუტები", + "search_results.total": "{count, number} {count, plural, one {result} other {results}}", + "standalone.public_title": "შიდა ხედი...", + "status.block": "დაბლოკე @{name}", + "status.cancel_reblog_private": "ბუსტის მოშორება", + "status.cannot_reblog": "ეს პოსტი ვერ დაიბუსტება", + "status.delete": "წაშლა", + "status.direct": "პირდაპირი წერილი @{name}-ს", + "status.embed": "ჩართვა", + "status.favourite": "ფავორიტი", + "status.filtered": "ფილტრირებული", + "status.load_more": "მეტის ჩატვირთვა", + "status.media_hidden": "მედია დამალულია", + "status.mention": "ასახელე @{name}", + "status.more": "მეტი", + "status.mute": "გააჩუმე @{name}", + "status.mute_conversation": "გააჩუმე საუბარი", + "status.open": "ამ სტატუსის გაფართოება", + "status.pin": "აპინე პროფილზე", + "status.pinned": "აპინული ტუტი", + "status.reblog": "ბუსტი", + "status.reblog_private": "დაიბუსტოს საწყის აუდიტორიაზე", + "status.reblogged_by": "{name} დაიბუსტა", + "status.redraft": "გაუქმდეს და გადანაწილდეს", + "status.reply": "პასუხი", + "status.replyAll": "უპასუხე თემას", + "status.report": "დაარეპორტე @{name}", + "status.sensitive_toggle": "დააწკაპუნეთ სანახავად", + "status.sensitive_warning": "მგრძნობიარე კონტენტი", + "status.share": "გაზიარება", + "status.show_less": "აჩვენე ნაკლები", + "status.show_less_all": "აჩვენე ნაკლები ყველაზე", + "status.show_more": "აჩვენე მეტი", + "status.show_more_all": "აჩვენე მეტი ყველაზე", + "status.unmute_conversation": "საუბარზე გაჩუმების მოშორება", + "status.unpin": "პროფილიდან პინის მოშორება", + "tabs_bar.federated_timeline": "ფედერალური", + "tabs_bar.home": "სახლი", + "tabs_bar.local_timeline": "ლოკალური", + "tabs_bar.notifications": "შეტყობინებები", + "tabs_bar.search": "ძებნა", + "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} საუბრობს", + "ui.beforeunload": "თქვენი დრაფტი გაუქმდება თუ დატოვებთ მასტოდონს.", + "upload_area.title": "გადმოწიეთ და ჩააგდეთ ასატვირთათ", + "upload_button.label": "მედიის დამატება", + "upload_form.description": "აღწერილობა ვიზუალურად უფასურისთვის", + "upload_form.focus": "კროპი", + "upload_form.undo": "გაუქმება", + "upload_progress.label": "იტვირთება...", + "video.close": "ვიდეოს დახურვა", + "video.exit_fullscreen": "სრულ ეკრანზე ჩვენების გათიშვა", + "video.expand": "ვიდეოს გაფართოება", + "video.fullscreen": "ჩვენება სრულ ეკრანზე", + "video.hide": "ვიდეოს დამალვა", + "video.mute": "ხმის გაჩუმება", + "video.pause": "პაუზა", + "video.play": "დაკვრა", + "video.unmute": "ხმის გაჩუმების მოშორება" +} diff --git a/app/javascript/mastodon/locales/whitelist_ka.json b/app/javascript/mastodon/locales/whitelist_ka.json new file mode 100644 index 000000000..0d4f101c7 --- /dev/null +++ b/app/javascript/mastodon/locales/whitelist_ka.json @@ -0,0 +1,2 @@ +[ +] diff --git a/config/application.rb b/config/application.rb index df64f0256..ea4949bf2 100644 --- a/config/application.rb +++ b/config/application.rb @@ -60,6 +60,7 @@ module Mastodon :io, :it, :ja, + :ka, :ko, :nl, :no, diff --git a/config/locales/activerecord.ka.yml b/config/locales/activerecord.ka.yml new file mode 100644 index 000000000..cdd4f9c4c --- /dev/null +++ b/config/locales/activerecord.ka.yml @@ -0,0 +1,13 @@ +--- +ka: + activerecord: + errors: + models: + account: + attributes: + username: + invalid: მხოლოდ ასოები, ციფრები და "ქვედა-ტირე" + status: + attributes: + reblog: + taken: სტატუსის უკვე არსებობს diff --git a/config/locales/devise.ka.yml b/config/locales/devise.ka.yml new file mode 100644 index 000000000..3267eb22e --- /dev/null +++ b/config/locales/devise.ka.yml @@ -0,0 +1,82 @@ +--- +ka: + devise: + confirmations: + confirmed: თქვენი ელ-ფოსტის მისამართი წარმატებით დამოწმდა. + send_instructions: თქვენ მიიღებთ ელ-ფოსტას ინსტრუქციებით თუ როგორც დაამოწმოთ თქვენი ელ-ფოსტის მისამართი რამდენიმე წუთში. გთხოვთ შეხედოთ თქვენი სპამის ფოლდერს თუ არ მიიღებთ ამ წერილს. + send_paranoid_instructions: თუ თქვენი ელ-ფოსტა არსებობს ჩვენს მონაცემთა ბაზაში, თქვენ მიიღებთ ელ-ფოსტას ინსტრუქციებით თუ როგორც დაამოწმოთ თქვენი ელ-ფოსტის მისამართი რამდენიმე წუთში. გთხოვთ შეხედოთ თქვენი სპამის ფოლდერს თუ არ მიიღებთ ამ წერილს. + failure: + already_authenticated: უკვე შესული ხართ. + inactive: თქვენი ანგარიში ჯერ არაა აქტივირებული. + invalid: არასწორი %{authentication_keys} ან პაროლი. + last_attempt: თქვენი ანგარიშის ჩაკეტვამდე დაგრჩათ კიდევ ერთი მცდელობა. + locked: თქვენი ანგარიში ჩაიკეტა. + not_found_in_database: არასწორი %{authentication_keys} ან პაროლი. + timeout: თქვენს სესიას გაუვიდა ვადა. გთხოვთ შედით ახლიდან რომ გააგრძელოთ. + unauthenticated: გაგრძელებამდე საჭიროა შეხვიდეთ ან დარეგისტრირდეთ. + unconfirmed: გაგრძელებამდე საჭიროა დაამოწმოთ თქვენი ელ-ფოსტა. + mailer: + confirmation_instructions: + action: დაამოწმეთ ელ-ფოსტის მისამართი + explanation: თქვენ ამ ელ-ფოსტის მისამართი ანგარიში შექმენით %{host}-ზე. დარჩა ერთი დაწკაპუნება მის აქტივაციამდე. თუ ეს თქვენ არ იყავით, გთხოვთ არ მიაქციოთ ყურადღება ამ წერილს. + extra_html: გთხოვთ ასევე გაეცნოთ ინსტანციის წესებს და ჩვენს კონფინდენციალურობის პოლიტიკას. + subject: 'მასტოდონი: დამოწმების ინსტრუქციები %{instance}-თვის' + title: ელ-ფოსტის მისამართის დამოწმება + email_changed: + explanation: 'თქვენი ანგარიშის ელ-ფოსტის მისამართი იცვლება შემდეგზე:' + extra: თუ თქვენ არ შეგიცვლიათ თქვენი ელ-ფოსტის მისამართი, როგორც ჩანს სხვამ ხელთ იგდო თქვენი ანგარიში. გთოხვთ შეცვალოთ თქვენი პაროლი რაც შეიძლება მალე, ან დაუკავშირდეთ ინსტანციის ადმინისტრატორს თუ თქვენი ანგარიში ჩაიკეტა. + subject: 'მასტოდონი: ელ-ფოსტა შეიცვალა' + title: ახალი ელ-ფოსტის მისამართი + password_change: + explanation: თქვენი ანგარიშის პაროლი შეიცვალა. + extra: თუ თქვენ არ შეგიცვლიათ პაროლი, როგორც ჩანს სხვამ ხელთ იგდო თქვენი ანგარიში. გთოხვთ შეცვალოთ თქვენი პაროლი რაც შეიძლება მალე, ან დაუკავშირდეთ ინსტანციის ადმინისტრატორს თუ თქვენი ანგარიში ჩაიკეტა. + subject: 'მასტოდონი: პაროლი შეიცვალა' + title: პაროლი შეიცვალა + reconfirmation_instructions: + explanation: დაამოწმეთ ახალი ელ-ფოსტის მისამართი ცვლილებისთვის. + extra: თუ თქვენ არ გამოიწვიეთ ეს ცვლილება, გთხოვთ არ მიაქციოთ ყურადღება ამ წერილს. მასტოდონის ელ-ფოსტის მისამართი არ შეიცვლება სანამ არ გადახვალთ ზემოთ მოცემულ ბმულზე. + subject: 'მასტოდონი: დაამოწმეთ ელ-ფოსტის მისამართი %{instance}-თვის' + title: დაამოწმეთ ელ-ფოსტის მისამართი + reset_password_instructions: + action: შეცვალეთ პაროლი + explanation: თქვენ მოითხოვეთ ახალი პაროლი თქვენი ანგარიშისთვის. + extra: თუ ეს თქვენ არ მოგითხოვიათ, გთხოვთ არ მიაქციოთ ყურადღება ამ წერილს. თქვენი პაროლი არ შეიცვლება, სანამ არ გადახვალთ ზემოთ მოცემულ ბმულზე. + subject: 'მასტოდონი: პაროლის განახლების ინსტრუქცეიბი' + title: პაროლის განახლება + unlock_instructions: + subject: 'მასტოდონი: ჩაკეტვის მოხსნის ინსტრუქციები' + omniauth_callbacks: + failure: 'ვერ მოხდა აუტენტიფიკაცია %{kind}-თან. მიზეზი: "%{reason}".' + success: წარმატებით შედგა აუტენტიფიკაცია %{kind} ანგარიშთან. + passwords: + no_token: ამ გვერდზე წვდომა ვერ გექნებათ თუ არ მოდიხართ პაროლის აღდგენის ელ-ფოსტის წერილიდან. თუ მოდიხართ პაროლის აღგენის წერილიდან, დაამოწმეთ რომ გადადიხართ სრულ ურლ-ზე. + send_instructions: თუ თქვენი ელ-ფოსტა არსებობს ჩვენს მონაცემთა ბაზაში, თქვენ მიიღებთ ელ-ფოსტაზე წერილს პაროლის განახლების ბმულით, რამდენიმე წუთში. გთხოვთ შეხედოთ თქვენი სპამის ფოლდერს თუ არ მიიღებთ ამ წერილს. + send_paranoid_instructions: თუ თქვენი ელ-ფოსტა არსებობს ჩვენს მონაცემთა ბაზაში, თქვენ მიიღებთ ელ-ფოსტაზე წერილს პაროლის განახლების ბმულით, რამდენიმე წუთში. გთხოვთ შეხედოთ თქვენი სპამის ფოლდერს თუ არ მიიღებთ ამ წერილს. + updated: თქვენი პაროლი წარმატებით შეიცვალა. ახლა შესული ხართ. + updated_not_active: თქვენი პაროლი წარმატებით შეიცვალა. + registrations: + destroyed: ნახვამდის! თქვენი ანგარიში წარმატებით გაუქმდა. იმედი გვაქვს ისევ შევხვდებით. + signed_up: გამარჯობა! თქვენ წარმატებით დარეგისტრირდით. + signed_up_but_inactive: თქვენ წარმატებით დარეგისტრირდით. თუმცა, ავტორიზაცია ვერ შედგა, თქვენი ანგარიში ჯერ არაა გააქტიურებული. + signed_up_but_locked: თქვენ წარმატებით დარეგისტრირდით. თუმცა, აცტორიზაცია ვერ შედგა, თქვენი ანგარიში ჩაკეტილია. + signed_up_but_unconfirmed: წერილი დამოწმების ბმულით თქვენს ელ-ფოსტაზე გამოგზავნილია. გთხოვთ გაჰყევით ბმულს, რათა გაააქტიუროთ ანგარიში. გთხოვთ შეხედოთ თქვენი სპამის ფოლდერს თუ არ მიიღებთ ამ წერილს. + update_needs_confirmation: თქვენი ანგარიში წარმატებით განახლდა, მაგრამ გვესაჭიროება თქვენი ელ-ფოსტის მისამართის დამოწმება. შეამოწმეთ ელ-ფოსტა და დასამოწმებლად გადადით მიღებულ ბმულზე. გთხოვთ შეხედოთ თქვენი სპამის ფოლდერს თუ არ მიიღებთ ამ წერილს. + updated: თქვენი ანგარიში წარმატებით განახლდა. + sessions: + already_signed_out: წარმატებით გახვედით. + signed_in: წარმატებით შეხვედით. + signed_out: წარმატებით გახვედით. + unlocks: + send_instructions: წერილს, ინსტრუქციებით თუ როგორ მოხსნათ ჩაკეტვა თქვენს ანგარიშს, მიიღებთ რამდენიმე წუთში. გთხოვთ შეხედოთ თქვენი სპამის ფოლდერს თუ არ მიიღებთ ამ წერილს. + send_paranoid_instructions: თუ თქვენი ელ-ფოსტა არსებობს ჩვენს მონაცემთა ბაზაში, თქვენ მიიღებთ ელ-ფოსტაზე წერილს ჩაკეტვის მოხნის ინსტრუქციებით. გთხოვთ შეხედოთ თქვენი სპამის ფოლდერს თუ არ მიიღებთ ამ წერილს. + unlocked: თქვენს ანგარიშს ჩაკეტვა წარმატებით მოეხსნა. გაგრძელებისთვის, გთხოვთ გაიაროთ ავტორიზაცია. + errors: + messages: + already_confirmed: უკვე დამოწმდა, გთხოვთ სცადოთ ავტორიზაციის გავლა + confirmation_period_expired: საჭიროებს დამოწმებას პერიოდში %{period}, გთხოვთ მოითხოვოთ ახლიდან + expired: გაუვიდა ვადა, გთხოვთ მოითხოვოთ ახალი + not_found: ვერ იქნა ნაპოვნი + not_locked: არ ჩაკეტილა + not_saved: + one: "%{resource} ვერ დამახსოვრდა ერთი შეცდომის გამო:" + other: "%{resource} ვერ დამახსოვრდა %{count} შეცდომის გამო:" diff --git a/config/locales/doorkeeper.ka.yml b/config/locales/doorkeeper.ka.yml new file mode 100644 index 000000000..e462e66f1 --- /dev/null +++ b/config/locales/doorkeeper.ka.yml @@ -0,0 +1,142 @@ +--- +ka: + activerecord: + attributes: + doorkeeper/application: + name: აპლიკაციის სახელი + redirect_uri: გადამისამართების ური + scopes: ფარგლები + website: აპლიკაციის ვებ-საიტი + errors: + models: + doorkeeper/application: + attributes: + redirect_uri: + fragment_present: ვერ ექნება ფრაგმეტი. + invalid_uri: უნდა იყოს ვალიდური ური. + relative_uri: უნდა იყოს აბსოლუტური ური. + secured_uri: უნდა იყოს ჰტტპს/სსლ ური. + doorkeeper: + applications: + buttons: + authorize: ავტორიზაცია + cancel: უარყოფა + destroy: გაუქმება + edit: შეცვლა + submit: გაგრძელება + confirmations: + destroy: დარწმუნებული ხართ? + edit: + title: აპლიკაციის შეცვლა + form: + error: უპს! შესაძლო შეცდომებზე შეამოწმეთ თქვენი ფორმა + help: + native_redirect_uri: ლოკალური ტესტებისთვის მოიხმარეთ %{native_redirect_uri} + redirect_uri: გამოიყენეთ ერთი ხაზი თითო ური-სთვის + scopes: ფარგლები გამოჰყავით სიცარიელით. საწყისი ფარგლის გამოსაყენებლად დატოვეთ ცარიელი. + index: + application: აპლიკაცია + callback_url: ქოლბექ ურლ + delete: გაუქმება + name: სახელი + new: ახალი აპლიკაცია + scopes: ფარგლები + show: ჩვენება + title: თქვენი აპლიკაციები + new: + title: ახალი აპლიკაცია + show: + actions: მოქმედებები + application_id: კლიენტის გასაღები + callback_urls: ქოლბექ ურლები + scopes: ფარგლები + secret: კლიენტის სერვერი + title: 'აპლიკაცია: %{name}' + authorizations: + buttons: + authorize: ავტორიზაცია + deny: აკრძალვა + error: + title: წარმოიშვა შეცდომა + new: + able_to: ის შეძლებს + prompt: აპლიკაცია %{client_name} ითხოვს წვდომას თქვენს ანგარიშზე + title: საჭიროა ავტორიზაცია + show: + title: დააკოპირეთ ეს ავტორიზაციის კოდი და ჩასვით აპლიკაციაში. + authorized_applications: + buttons: + revoke: გაუქმება + confirmations: + revoke: დარწმუნებული ხართ? + index: + application: აპლიკაცია + created_at: ავტორიზებული + date_format: "%Y-%m-%d %H:%M:%S" + scopes: ფარგლები + title: თქვენი ავტორიზებული აპლიკაციები + errors: + messages: + access_denied: რესურსის მფლობელმა ან აუტორიზაციის სერვერმა აკრძალა ეს მოთხოვნა. + credential_flow_not_configured: რესურის მფლობელის პაროლის რწმუნებულებების ნაკადი ვერ შესრულდა არაკონფიგურირებული Doorkeeper.configure.resource_owner_from_credentials გამო. + invalid_client: ამოუცნობი კლიენტის გამო კლიენტ აუტენტიფიკაცია ვერ მოხერხდა, კლიენტის აუტენტიფიკაცია არ იყო თან დართული, ან მხარდაუჭერელი აუტენტიფიკაციის მეთოდი. + invalid_grant: მოწოდებული ავტორიზაციის გრანტი არასწორია, ვადაგასულია, გაუქმებულია არ ემთხვევა გადამისამართების ურის, რომელიც მოიხმარება ავტორიზაცის მოთხოვნაში, ან მიეცა სხვა კლიენტს. + invalid_redirect_uri: მითითებული გადამისამართების ური არაა ვალიდური. + invalid_request: მოთხოვნას აკლია აუცილებელი პარამეტრი, მოიცავს მხარდაუჭერელ პარამეტრის მნიშვნელობას, ან სხვაგვარად არაა გამართული. + invalid_resource_owner: მოწოდებული რესურსის მფლობელის რწმუნებულებები არაა ვალიდური, ან მფლობელის პონვა ვერ ხერხდება + invalid_scope: მოთხოვნილი ფარგალი არასწორია, ამოუცნობია ან არაა გამართული. + invalid_token: + expired: წვდომის ტოკენს გაუვიდა ვადა + revoked: წვდომის ტოკენი გაუქმდა + unknown: წვდომის ტოკენი არაა ვალიდური + resource_owner_authenticator_not_configured: რესურსის მფლობელის მოპოვება არ შედგა Doorkeeper.configure.resource_owner_authenticator კონფიგურაციის არ არსებობის გამო. + server_error: აუტორიზაციის სერვერს შეხვდა მოულოდნელი მდგომარეობა, რამაც ხელი შეუშალა მას აღესრულებინა მოთხონვა. + temporarily_unavailable: ავტორიზაციის სერვერი ამჟამად ვერ ახერხებს მოთხოვნის შემუშავებას დროებითი გადატვირთვის ან სერვერის შენარჩუნების გამო. + unauthorized_client: კლიენტი არაა ავტორიზებული შეასრულოს ეს მოთხოვნა ამ მეთოდით. + unsupported_grant_type: ავტორიზაციის გრანტის სახეობა არაა მხარდაჭერილი ავტორიზაციის სერვერის მიერ. + unsupported_response_type: ავტორიზაციის სერვერი არ უჭერს მხარს ამ პასუხის სახეობას. + flash: + applications: + create: + notice: აპლიკაცია შეიქმნა. + destroy: + notice: აპლიკაცია გაუქმდა. + update: + notice: აპლიკაცია განახლდა. + authorized_applications: + destroy: + notice: აპლიკაცია წაიშალა. + layouts: + admin: + nav: + applications: აპლიკაციები + oauth2_provider: ოუ-აუთ2 პროვაიდერი + application: + title: საჭიროა ოუ-აუთ ავტორიზაცია + scopes: + follow: შეცვალეთ ანგარიშის ურთიერთობები + push: მიიღეთ თქვენი ფუშ შეტყობინებები + read: წაიკითხოს მთელი თქვენი ანგარიშის მონაცემები + read:accounts: იხილოს ანგარიშის ინფორმაცია + read:blocks: იხილოს თქვენი ბლოკები + read:favourites: იხილოს თქვენი ფავორიტები + read:filters: იხილოს თქვენი ფილრები + read:follows: იხილოს თქვენი მიდევნებები + read:lists: იხილოს თქვენი სიები + read:mutes: იხილოს თქვენი გაჩუმებები + read:notifications: იხილოს თქვენი შეტყობინებები + read:reports: იხილოს თქვენი რეპორტები + read:search: მოძებნოს თქვენი სახელით + read:statuses: იხილოს ყველა სტატუსი + write: შეცვალოს მთელი თქვენი ანგარიშის მონაცემები + write:accounts: შეცვალოს თქვენი პროფილი + write:blocks: დაბლოკოს ანგარიშები და დომენები + write:favourites: ფავორიტი სტატუსები + write:filters: შექმნას ფილტრები + write:follows: გაყვეს ხალხს + write:lists: შექმნას სიები + write:media: ატვირთოს მედია ფაილები + write:mutes: გააგჩუმოს ადამიანები და საუბრები + write:notifications: გაასუფთავოს თქვენი შეტყობინებები + write:reports: დაარეპორტოს სხვა ადამიანები + write:statuses: გამოაქვეყნოს სტატუსები diff --git a/config/locales/ka.yml b/config/locales/ka.yml new file mode 100644 index 000000000..86d982665 --- /dev/null +++ b/config/locales/ka.yml @@ -0,0 +1,893 @@ +--- +ka: + about: + about_hashtag_html: ეს საჯარო ტუტებია, რომლებიც ატარებენ #%{hashtag} ტეგს. მათთან ინტერაქციას შეძლებთ, თუ ფედივერსში გაქვთ რაიმე ანგარიში. + about_mastodon_html: მასტოდონი ღია ვებ პროტოკოლებზე და უფასო, ღია პროგრამებზე დაფუძნებული სოციალური ქსელია. ის ისეთი დეცენტრალიზებულია როგორც ელ-ფოსტა. + about_this: შესახებ + administered_by: 'ადმინისტრატორი:' + api: აპი + closed_registrations: რეგისტრაციები ამჟამად ინსტანციაზე დახურულია. თუმცა! ანგარიშის შესაქმნელად შეგიძლიათ იპოვოთ სხვა ინსტანცია და იმავე ქსელზე იქონიოთ წვდომა იქიდან. + contact: კონტაქტი + contact_missing: არაა დაყენებული + contact_unavailable: მიუწ. + documentation: დოკუმენტაცია + extended_description_html: | +

კარგი ადგილი წესებისთვის

+

განვრცობილი აღწერილობა ჯერ არ შექმნილა.

+ features: + humane_approach_body: სხვა ქსელების შეცდომების გათვალისწინებით, მასტოდონი მიზნად ისახავს ეტიკური დიზაინის არჩევნების გაკეთებას, დაუპირისპირდეს სოციალური მედიის არასწორ მოხმარებას. + humane_approach_title: უფრო ადამიანური მიდგომა + not_a_product_body: მასტოდონი არ არის კომერციული ქსელი. არაა რეკლამა, არაა მაინინგი, არაა შემოღობილი ბაღები. არაა ცენტრალური ავტორიტეტი. + not_a_product_title: შენ ხარ პერსონა და არა პროდუქტი + real_conversation_body: 500 ნიშნის განკარგულებით, მარცვლოვანი კონტენტის და მედია გაფრთხილებების მხარდაჭერით, შეგიძლიათ გამოხატოთ ისე როგორც გსურთ. + real_conversation_title: შექმნილია ნამდვილი საუბრისთვის + within_reach_body: დეველოპერისთვის-მეგობრული აპი ექოსისტემის წყალობით, მრავალი აპლიკაცია აი-ოსისთვის, ანდროიდისთვის და სხვა პლატფორმებისთვის, საშალებას მოგცემთ ნებისმიერი ადგილიდან იქონიოთ კავშირი თქვენს მეგობრებთან. + within_reach_title: მუდამ წვდომის ქვეშ + generic_description: "%{domain} ერთი სერვერია ქსელში" + hosted_on: მასტოდონს მასპინძლობს %{domain} + learn_more: გაიგე მეტი + other_instances: ინსტანციების სია + privacy_policy: კონფიდენციალურობის პოლიტიკა + source_code: კოდი + status_count_after: სტატუსები + status_count_before: ვინც უავტორა + terms: მომსახურების პირობები + user_count_after: მომხმარებლისთვის + user_count_before: სახლი + what_is_mastodon: რა არის მასტოდონი? + accounts: + choices_html: "%{name}-ის არჩევნები:" + follow: გაყევი + followers: მიმდევრები + following: მიჰყვება + joined: გაწევრიანდა %{date} + media: მედია + moved_html: "%{name} გადავიდა %{new_profile_link}:" + network_hidden: ეს ინფორმაცია ხელმიუწვდომელია + nothing_here: აქ არაფერია! + people_followed_by: ხალხი ვისაც %{name} მიჰყვება + people_who_follow: ხალხი ვინც მიჰყვება %{name}-ს + pin_errors: + following: იმ ადამიანს, ვინც მოგწონთ, უკვე უნდა მიჰყვებოდეთ + posts: ტუტები + posts_with_replies: ტუტები და პასუხები + reserved_username: მომხმარებელი რეზერვირებულია + roles: + admin: ადმინისტრატორი + bot: ბოტი + moderator: მოდერატორი + unfollow: ნუღარ მიჰყვები + admin: + account_moderation_notes: + create: დატოვეთ ჩანაწერი + created_msg: მოდერაციის ჩანაწერი წარმატებით შეიქმნა! + delete: გაუქმება + destroyed_msg: მოდერაციის ჩანაწერი წარმატებით გაუქმდა! + accounts: + are_you_sure: დარწმუნებული ხარ? + avatar: ავატარი + by_domain: დომენი + change_email: + changed_msg: ანგარიშის ელ-ფოსტა წარმატებით შეიცვალა! + current_email: მიმდინარე ელ-ფოსტა + label: ელ-ფოსტის შეცვლა + new_email: ახალი ელ-ფოსტა + submit: ელ-ფოსტის შეცვლა + title: შეცვალეთ ელ-ფოსტა მომხმარებლისთვის %{username} + confirm: დადასტურება + confirmed: დადასტურებულია + confirming: დასტურდება + demote: დაქვეითება + disable: გამორთვა + disable_two_factor_authentication: გამორთე 2FA + disabled: გამორთულია + display_name: დისპლეი სახელი + domain: დომენი + edit: შეცვლა + email: ელ-ფოსტა + email_status: ელ-ფოსტის სტატუსი + enable: ჩართვა + enabled: ჩართულია + feed_url: ლენტის ურლ + followers: მიმდევრები + followers_url: მიმდევრების ურლ + follows: დადევნებები + inbox_url: ინბოქსის ურლ + ip: აი-პი + location: + all: ყველა + local: ლოკალური + remote: დისტანციური + title: ადგილმდებარეობა + login_status: ლოგინის სტატუსი + media_attachments: თან-დართული მედია + memorialize: აქციე მემორანდუმად + moderation: + all: ყველა + silenced: გაჩუმებული + suspended: შეჩერებული + title: მოდერაცია + moderation_notes: მოდერაციის ჩანაწერები + most_recent_activity: უახლესი აქტივობა + most_recent_ip: უახლესი აი-პი + not_subscribed: გამოუწერელი + order: + alphabetic: ანბანური + most_recent: უახლესი + title: წესრიგი + outbox_url: აუთბოქსის ურლ + perform_full_suspension: მოახდინეთ სრული შეჩერება + profile_url: პროფილის ურლ + promote: დაწინაურება + protocol: პროტოკოლი + public: საჯარო + push_subscription_expires: ფუშ გამოწერა უქმდება + redownload: განაახლე ავატარი + remove_avatar: გააუქმე ავატარი + resend_confirmation: + already_confirmed: ეს მომხმარებელი უკვე დამოწმებულია + send: დამოწმების ინსტრუქციების გადაგზავნა + success: დამოწმების ინსტრუქციები წარმატებით გაიგზავნა! + reset: გადატვირთვა + reset_password: პაროლის გადატვირთვა + resubscribe: ხელახალი გამოწერა + role: უფლებები + roles: + admin: ადმინისტრატორი + moderator: მოდერატორი + staff: სტაფი + user: მომხმარებელი + salmon_url: სალმონის ურლ + search: ძებნა + shared_inbox_url: გაზიარებული ინბოქსის ურლ + show: + created_reports: ამ ანგარიშის მიერ შექმნილი რეპორტები + report: რეპორტი + targeted_reports: ამ ანგარიშზე მიღებული რეპორტები + silence: სიჩუმე + statuses: სტატუსები + subscribe: გამოწერა + title: ანგარიშები + unconfirmed_email: დაუმოწმებელი ელ-ფოსტა + undo_silenced: გაჩუმების მოშორება + undo_suspension: შეჩერების მოშორება + unsubscribe: გამოწერის შეწყვეტა + username: მომხმარებლის სახელი + web: ვები + action_logs: + actions: + assigned_to_self_report: "%{name}-მა დანიშნა რეპორტი %{target} საკუთარ თავზე" + change_email_user: "%{name}-მა შეცვალა %{target} მომხმარებლის ელ-ფოსტის მისამართი" + confirm_user: "%{name}-მა დაამოწმა %{target} მომხმარებლის ელ-ფოსტის მისამართი" + create_custom_emoji: "%{name}-მა ატვირთა ახალი ემოჯი %{target}" + create_domain_block: "%{name}-მა დაბლოკა დომენი %{target}" + create_email_domain_block: "%{name}-მა შავ სიაში მოაქცია დომენი %{target}" + demote_user: "%{name}-მა დააქვეითა მომხმარებელი %{target}" + destroy_domain_block: "%{name}-მა ბლოკი მოხსნა დომენს %{target}" + destroy_email_domain_block: "%{name} თეთრ სიაში მოაქცია დომენი %{target}" + destroy_status: "%{name}-მა გააუქმა სტატუსი %{target}-ზე" + disable_2fa_user: "%{name} გათიშა მეორე ფაქტორის მოთხოვნილება მომხმარებელზე %{target}" + disable_custom_emoji: "%{name}-მა გათისა ემოჯი %{target}" + disable_user: "%{name}-მა გათიშა ლოგინი მომხმარებლისთვის %{target}" + enable_custom_emoji: "%{name}-მა ჩართო ემოჯი %{target}" + enable_user: "%{name}-მა ჩართო ლოგინი მომხმარებლისთვის %{target}" + memorialize_account: "%{name}-მა აქცია ანგარიში %{target} მემორანდუმის გვერდად" + promote_user: "%{name}-მა დააწინაურა მომხმარებელი %{target}" + remove_avatar_user: "%{name}-მა გააუქმა %{target} მომხმარებლის ავატარი" + reopen_report: "%{name}-მა ხელახლა გახსნა რეპორტი %{target}" + reset_password_user: "%{name} გადატვირთა მომხმარებლის %{target} პაროლი" + resolve_report: "%{name}-მა მოაგვარა %{target} მომხმარებლის რეპორტი" + silence_account: "%{name}-მა გააჩუმა %{target} ანგარიში" + suspend_account: "%{name} შეაჩერა %{target} ანგარიში" + unassigned_report: "%{name}-მა მოაშორა რეპორტი %{target}" + unsilence_account: "%{name}-მა მოაშორა გაჩუმება %{target} ანგარიშს" + unsuspend_account: "%{name}-მა მოაშორა შეჩერება %{target} ანგარიშს" + update_custom_emoji: "%{name}-მა განაახლა ემოჯი %{target}" + update_status: "%{name}-მა განაახლა სტატუსი %{target}-ით" + deleted_status: "(გაუქმებული სტატუსი)" + title: აუდიტის ლოგი + custom_emojis: + by_domain: დომენი + copied_msg: ემოჯის ლოკალური ასლი წარმატებით შეიქმნა + copy: კოპირება + copy_failed_msg: ამ ემოჯის ლოკალური ასლი ვერ შეიქმნა + created_msg: ემოჯი წარმატებით შეიქმნა! + delete: გაუქმება + destroyed_msg: ემოჯი წარმატებით გაუქმდა! + disable: გათიშვა + disabled_msg: ეს ემოჯი წარმატებით გაითიშა + emoji: ემოჯი + enable: ჩართვა + enabled_msg: წარმატებით ჩაირთო ეს ემოჯი + image_hint: PNG 50კბმდე + listed: ჩამოთვლილი + new: + title: ახალი პერსონალიზირებული ემოჯის დამატება + overwrite: გადაწერა + shortcode: მოკლე-კოდი + shortcode_hint: მინ. 2 ნიშანი, მხოლოდ ალფანუმერიკული ნიშნები და "ქვედა-ტირეები" + title: პერსონალიზირებული ემოჯიები + unlisted: ჩამოუთვლელი + update_failed_msg: ემოჯის განახლება ვერ მოხერხდა + updated_msg: ემოჯი წარმატებით განახლდა! + upload: ატვირთვა + dashboard: + backlog: დაუსრულებელი საქმეები + config: კონფიგურაცია + feature_deletions: ანგარიშის გაუქმებები + feature_invites: მოწვევის ბმულები + feature_registrations: რეგისტრაციები + feature_relay: ფედერაციის რილეი + features: ფუნქციები + hidden_service: ფედერაცია დამალულ სერვისებთან + open_reports: ღია რეპორტები + recent_users: ახალი მომხმარებლები + search: სრული-ტექსტის ძიება + single_user_mode: ერთ-მომხმარებლიანი რეჟიმი + software: პროგრამა + space: მოცულობის მოხმარება + title: დაფა + total_users: სულ მომხმარებლები + trends: ტრენდები + week_interactions: ამ კვირის ინტერაქციები + week_users_active: აქტიური ამ კვირას + week_users_new: ამ კვირის მომხმარებლები + domain_blocks: + add_new: ახლის დამატება + created_msg: დომენის ბლოკი ახლა პროცესირების ქვეშაა + destroyed_msg: დომენის ბლოკი გაუქმდა + domain: დომენი + new: + create: ბლოკის შექმნა + hint: დომენის ბლოკი არ შეაჩერებს ანგარიშების ჩაწერას მონაცემთა ბაზაში, მაგრამ ეს ამ ანგარიშებზე რეტროაქტიულად და ავტომატურად გაატარებს სპეციფიურ მოდერაციის მეთოდებს. + severity: + desc_html: "გაჩუმება გახდის ანგარიშის პოსტებს უჩინარს ყველასთვის, ვინც მას არ მიჰყვება. შეჩერება გააუქმებს ანგარიშის მთელ კონტენტს, მედიას და პროფილის მონაცემს. გამოიყენეთ არც ერთი თუ გსურთ უბრალოდ უარყოთ ფაილები." + noop: არც ერთი + silence: გაჩუმება + suspend: შეჩერება + title: ახალი დომენის ბლოკი + reject_media: მედია ფაილების უარყოფა + reject_media_hint: შლის ლოკალურად შენახულ მედია ფაილებს და უარყოფს სამომავლო გადმოტვირთებს. შეუსაბამო შეჩერებებისთვის + severities: + noop: არც ერთი + silence: გაჩუმება + suspend: შეჩერება + severity: სიმძიმე + show: + affected_accounts: + one: გავლენა იქონია მონაცემთა ბაზაში ერთ ანგარიშზე + other: გავლენა იქონიო მონაცემთა ბაზაში %{count} ანგარიშზე + retroactive: + silence: ამ დომენში ყველა არსებულ ანგარიშზე გაჩუმების მოშორება + suspend: ამ დომენში ყველა არსებულ ანგარიშზე შეჩერების მოშორება + title: უკუაქციეთ დომენის ბლოკი %{domain} დომენზე + undo: უკუქცევა + title: დომენის ბლოკები + undo: უკუქცევა + email_domain_blocks: + add_new: ახლის დამატება + created_msg: ელ-ფოსტის დომენი წარმატებით დაემატა შავ სიას + delete: გაუქმება + destroyed_msg: ელ-ფოსტის დომენი წარმატებით ამოიშალა შავი სიიდან + domain: დომენი + new: + create: დომენის დამატება + title: ელ-ფოსტის ახალი შენატანი შავ სიაში + title: ელ-ფოსტის შავი სია + instances: + account_count: ცნობილი ანგარიშები + domain_name: დომენი + reset: გადატვირთვა + search: ძებნა + title: ცნობილი ინსტანციები + invites: + filter: + all: ყველა + available: ხელმისაწვდომი + expired: ვადაგასული + title: ფილტრი + title: მოწვევები + relays: + add_new: ახელი რილეი + description_html: "ფედერაციის რილეი შუამავალი სერვერია, რომელიც ცვლის საჯარო ტუტების დიდ ოდენობას იმ სერვერებს შორის, რომლებიც გამოიწერენ და მასზე გამოაქვეყნებენ. ეს მცირე და საშუალო სერვერებს ეხმარება აღმოაჩინონ კონტენტი ფედივერსისგან, რომელიც სხვა შემთხვევაში მომხარებლებს აიძულებდა მექნიკურ რეჟიმში გაჰყოლოდნენ ხალხს სხვა დისტანციურ სერვერებზე." + enable_hint: ამოქმდების შემდეგ, თქვენი სერვერი გამოიწერს ყველა საჯარო ტუტს ამ რილეიდან და დაიწყებს სერვერის ღია ტუტების იქ გაგზავნას. + inbox_url: რილეი ურლ + setup: რილეი კავშირის დამყარება + status: სტატუსი + title: რილეი სია + report_notes: + created_msg: რეპორტის ჩანაწერი წარმატებით შეიქმნა! + destroyed_msg: რეპორტის ჩანაწერი წარმატებით გაუქმდა! + reports: + account: + note: ჩანაწერი + report: რეპორტი + action_taken_by: მოქმედება შეასრულა + are_you_sure: დარწმუნებული ხარ? + assign_to_self: დანიშნე ჩემზე + assigned: დაინიშნა მოდერატორი + comment: + none: არაფერი + created_at: რეპორტის დრო + id: იდ + mark_as_resolved: მონიშნე გადაწყვეტილად + mark_as_unresolved: მონიშნე გადაუწყვეტლად + notes: + create: ჩანაწერის დამატება + create_and_resolve: გადაწყვეტა ჩანაწერით + create_and_unresolve: ხელახალი გახსნა ჩანაწერით + delete: გაუქმება + placeholder: აღწერეთ თუ რა ნაბიჯები უნდა გადაიდგას, ან სხვა დაკავშირებული განახლებები... + reopen: რეპორტის ხელახალი გახსნა + report: 'რეპორტი #%{id}' + report_contents: მოცულობები + reported_account: დარეპორტებული ანგარიში + reported_by: დაარეპორტა + resolved: გადაწყვეტილი + resolved_msg: რეპორტი წარმატებით გადაწყდა! + silence_account: ანგარიშის გაჩუმება + status: სტატუსი + suspend_account: ანგარიშის შეჩერება + target: მიზანი + title: რეპორტები + unassign: გადაყენება + unresolved: გადაუწყვეტელი + updated_at: განახების დრო + view: ჩვენება + settings: + activity_api_enabled: + desc_html: ლოკალურად გამოქვეყნებული სტატუსების, აქტიური მომხმარებლების და ყოველკვირეული რეგისტრაციების მთვლელი + title: გამოაქვეყნე აგრეგატი სტატისტიკები მომხმარებლის აქტივობაზე + bootstrap_timeline_accounts: + desc_html: გამოჰყავი მომხმარებლები მძიმით. იმუშავებს მხოლოდ ლოკალური და "ბლოკ-მოხსნილ" ანგარიშები. საწყისი როდესაც ცარიელია ყველა ლოკალური ადმინი. + title: საწყისი მიდევნებები ახლა მომხმარებლებზე + contact_information: + email: ბიზნეს ელ-ფოსტა + username: საკონტაქტო მომხმარებლის სახელი + hero: + desc_html: წინა გვერდზე გამოჩენილი. მინ. 600/100პიქს. რეკომენდირებული. როდესაც არაა დაყენებული, ჩნდება ინსტანციის პიქტოგრამა + title: გმირი სურათი + peers_api_enabled: + desc_html: დომენების სახელები რომლებსაც შეხვდა ეს ინსტანცია ფედივერსში + title: გამოაქვეყნე აღმოჩენილი ინსტანციების სია + preview_sensitive_media: + desc_html: ბმულის პრევიუები სხვა ვებ-საიტებზე გამოაჩენენ პიქტოგრამას, მაშინაც კი თუ მედია მონიშნულია მგრძნობიარედ + title: გამოაჩინე მგრძნობიარე მედია ოუფენ-გრეფ პრევიუებში + registrations: + closed_message: + desc_html: გამოჩნდება წინა გვერდზე, როდესაც რეგისტრაციები დახურულია. შეგიძლიათ გამოიყენოთ ჰტმლ ტეგები + title: დახურული რეგისტრაციის წერილი + deletion: + desc_html: უფლება მიეცით ყველას, გააუქმონ თავიანთი ანგარიში + title: ღია ანგარიშის გაუქმება + min_invite_role: + disabled: არავინ + title: ნება დაერთოს მოწვეევებს + open: + desc_html: უფლება მიეცით ყველას, გახსნან ანგარიში + title: ღია რეგისტრაცია + show_known_fediverse_at_about_page: + desc_html: ჩართვისას, ეს გამოაჩენს ტუტებს ყველა ცნობილი ფედივერსისგან პრევიუზე. სხვა შემთხვევაში, გამოაჩენს მხოლოდ ლოკალურ ტუტებს. + title: გამოჩნდეს ცნობილი ვედივერსი თაიმლაინ პრევიუში + show_staff_badge: + desc_html: გამოჩნდეს სტაფის ნიშანი მომხმარებლის გვერდზე + title: სტაფის ნიშნის გამოჩენა + site_description: + desc_html: საშესავლო პარაგრაფი წინა გვერდზე. აღწერეთ თუ რა ხდის ამ მასტოდონის სერვერს განსაკუთრებულს და სხვა მნიშვნელოვანი. შეგიძლიათ გამოიყენოთ ჰტმლ ტეგები, კერძოდ <a> და <em>. + title: ინსტანციის აღწერილობა + site_description_extended: + desc_html: კარგი ადგილი მოქცევის კოდექსისთვის, წესები, სახელმძღვანელოები და სხვა რაც გამოარჩევს თქვენს ინსტანციას. შეგიძლიათ გამოიყენოთ ჰტმლ ტეგები + title: პერსონალიზირებული განვრცობილი ინფორმაცია + site_short_description: + desc_html: გამოჩნდება გვერდით ბარში და მეტა ტეგებში. აღწერეთ თუ რა არის მასტოდონი და რა ხდის ამ სერვერს უნიკალურს ერთ პარაგრაფში. თუ ცარიელია, გამოჩნდება ინსტანციის აღწერილობა. + title: აჩვენეთ ინსტანციის აღწერილობა + site_terms: + desc_html: შეგიძლიათ დაწეროთ საკუთარი კონფიდენციალურობის პოლიტიკა, მომსახურების პირობები ან სხვა იურიდიული დოკუმენტი. შეგიძლიათ გამოიყენოთ ჰტმლ ტეგები + title: პერსონალიზირებული მომსახურების პირობები + site_title: ინსტანციის სახელი + thumbnail: + desc_html: გამოიყენება პრევიუებისთვის ოუფენ-გრეფში და აპი-ში. 1200/630პიქს. რეკომენდირებული + title: ინსტანციის პიქტოგრამა + timeline_preview: + desc_html: აჩვენეთ საჯარო თაიმლაინი ლენდინგ გვერდზე + title: თაიმლაინ პრევიუ + title: საიტის პარამეტრები + statuses: + back_to_account: უკან ანგარიშის გვერდისკენ + batch: + delete: გაუქმება + nsfw_off: მონიშნე არა-მგრძნობიარედ + nsfw_on: მონიშნე მგრძნობიარედ + failed_to_execute: ვერ გაეშვა + media: + title: მედია + no_media: არაა მედია + no_status_selected: სატუსები არ შეცვლილა, რადგან არცერთი არ მონიშნულა + title: ანგარიშის სტატუსები + with_media: მედიით + subscriptions: + callback_url: ქოლბექ ურლ + confirmed: დამოწმდა + expires_in: ვადა გასდის + last_delivery: ბოლო მიღება + title: ვებ-საბი + topic: სათაური + title: ადმინისტრაცია + admin_mailer: + new_report: + body: "%{reporter}-მა დაარეპორტა %{target}" + body_remote: ვიღაცამ %{domain}-იდან დაარეპორტა %{target} + subject: ახალი რეპორტი %{instance} (#%{id})-ზე + application_mailer: + notification_preferences: შეცვალეთ ელ-ფოსტის პრეფერნსიები + salutation: "%{name}," + settings: 'შეცვალეთ ელ-ფოსტის პრეფერენსიები: %{link}' + view: 'ჩვენება:' + view_profile: პროფილის ჩვენება + view_status: სტატუსის ჩვენება + applications: + created: აპლიკაცია წარმატებით შეიქმნა + destroyed: აპლიკაცია წარმატებით გაუქმდა + invalid_url: მოწოდებული ურლ არასწორია + regenerate_token: წვდომის ტოკენის რეგენერაცია + token_regenerated: წვდომის ტოკენის რეგენერაცია მოხერხდა + warning: იყავით ძალიან ფრთხილად ამ მონაცემთან. არასდროს გააზიაროთ ეს! + your_token: თქვენი წვდომის ტოკენი + auth: + agreement_html: რეგისტრაციით თქვენ ეთანხმებით ინსტანციის წესებს და ჩვენ მომსახურების პირობებს. + change_password: პაროლი + confirm_email: ელ-ფოსტის დამოწმება + delete_account: ანგარიშის გაუქმება + delete_account_html: თუ გსურთ გააუქმოთ თქვენი ანგარიში, შეგიძლიათ გააგრძელოთ აქ. საჭირო იქნება დამოწმება. + didnt_get_confirmation: არ მოგსვლიათ დამოწმების ინსტრუქციები? + forgot_password: დაგავიწყდათ პაროლი? + invalid_reset_password_token: პაროლის გადატვირთვის ტოკენი არასწორია ან ვადაგასული. გთხოვთ მოითხოვეთ ახალი. + login: შესვლა + logout: გასვლა + migrate_account: სხვა ანგარიშზე გადასვლა + migrate_account_html: თუ გსურთ ამ ანგარიშის რედირექტის ხვაზე, შეგიძლიათ გაუწიოთ კონფიგურაცია აქ. + or: ან + or_log_in_with: ან გამოიყენეთ + providers: + cas: ქეს + saml: სამლ + register: რეგისტრაცია + register_elsewhere: რეგისტრაცია სხვა სერვერზე + resend_confirmation: დამოწმების ინსტრუქციების ხელახალი გამოგზავნა + reset_password: პაროლის გადატვირთვა + security: უსაფრთხოება + set_new_password: ახალი პაროლის დაყენება + authorize_follow: + already_following: უკვე მიჰყვებით ამ ანგარიშს + error: სამწუხაროთ, დისტანციური სერვერის წაკითხვამ გამოიწვია შეცდომა + follow: გაყევი + follow_request: 'დადევნების მოთხონვა გაეგზავნა:' + following: 'წარმატება! ახლა მიჰყვებით:' + post_follow: + close: ან შეგიძლიათ დახუროთ ეს ფანჯარა. + return: მომხმარებლის პროფილის ჩვენება + web: ვებზე გადასვლა + title: გაყევი %{acct}-ს + datetime: + distance_in_words: + about_x_hours: "%{count}სთ" + about_x_months: "%{count}თვე" + about_x_years: "%{count}წელი" + almost_x_years: "%{count}წელი" + half_a_minute: ამ წამს + less_than_x_minutes: "%{count}წთ" + less_than_x_seconds: ამ წამს + over_x_years: "%{count}წელი" + x_days: "%{count}დღე" + x_minutes: "%{count}წთ" + x_months: "%{count}თვე" + x_seconds: "%{count}წმ" + deletes: + bad_password_msg: კარგად სცადეთ, ჰაკერებო! არასწორი პაროლი + confirm_password: იდენტობის დასამოწმებლად შეიყვანეთ მიმდინარე პაროლი + description_html: ეს სამუდამოდ, დაუბრუნებლად გააუქმებს კონტენტს თქვენი ანგარიშიდან და მოახდენს მის დეაქტივაციას. მომხმარებლის სახელი კი, სამომავლო იმპერსონაციების შესაჩერებლად, გახდება რეზერვირებული + proceed: ანგარიშის გაუქმება + success_msg: თქვენი ანგარიში წარმატებით გაუქმდა + warning_html: მოცულობის გაუქმება გარანტირებულია მხოლოდ ამ ინსტანციაზე. კონტენტი რომელიც ფართო მასშტაბით გაზიარდა უფრო დატოვებს კვალს. ოფლაინ სერვერები და სერვერები, რომლებმაც შეწყვიტეს თქვენი განახლებების გამოწერა არ განაახლებენ მონაცემთა ბაზებს. + warning_title: წვდომა გავრცელებულ კონტენტზე + errors: + '403': ამ გვერდის ხილვის უფლება არ გაქვთ. + '404': გვერდი რომელსაც ეძებთ არ არსებობს. + '410': გვერდი რომელსაც ეძებდით აღარ არსებობს. + '422': + content: უსაფრთხოების ვერიფიკაცია ვერ მოხერხდა. ბლოკავთ ქუქის? + title: უსაფრთხოების ვერიფიკაცია არ შედგა + '429': დარტყმა + '500': + content: ბოდიში, ჩვენ მხარეს რაღაც არია. + title: გვერდი არაა სწორი + noscript_html: მასტოდონ ვებ-აპლიკაციის გამოყენებისთვის, გთხოვთ ჩართოთ ჯავასკრიპტი. სხვა შემთხვევაში, მასტოდონის თქვენი პატფორმისთვის სცადეთ გამოიყენოთ ერთ-ერთი მშობლიური აპლიკაცია. + exports: + archive_takeout: + date: თარიღი + download: ჩამოტვირთეთ თქვენი არქივი + hint_html: შეგიძლიათ მოითხოვოთ თქვენი აქივი ტუტებისა და ატვირთული მედიისა. ექსპორტირებული მონაცემები იქნება ექთივითი-ფაბ ფორმატში, წაკითხვადი ნებისმიერი თავსებადი პროგრამით. არქივის მოთხოვნა შეგიძლიათ 7 დღეში ერთხელ. + in_progress: მიმდინარეობს თქვენი არქივის შედგენა... + request: თქვენი არქივის მოთხოვნა + size: ზომა + blocks: თქვენ ბლოკავთ + csv: ცსვ + follows: თქვენ მიჰყვებით + mutes: თქვენ აჩუმებთ + storage: მედია საცავი + filters: + contexts: + home: სახლის თაიმლაინი + notifications: შეტყობინებები + public: საჯარო თაიმლაინი + thread: საუბრები + edit: + title: ფილტრის ცვლილება + errors: + invalid_context: მოწოდებულია არასწორი ან ცარიელი კონტექსტი + invalid_irreversible: დაუბრუნებელი ფილტრაცია მუშაობს მხოლოდ სახლის ან ნოტიფიკაციის კონტექსტში + index: + delete: გაუქმება + title: ფილტრები + new: + title: ახალი ფილტრის დამატება + followers: + domain: დომენი + explanation_html: თუ გსურთ უზრუნველყოთ თქვენი სტატუსების კონფიდენციალურობა, უნდა იცოდეთ თუ ვინ მოგყვებათ. კერძო სტატუსები მიეწოდება ყველა ინსტანციას, სადაც გყავთ მიმდევრები. შესაძლოა გსურდეთ განიხილოთ ისინი და ამოშალოთ მიმდევრები თუ არ ენდობით თქვენი კონფიდენციალურობის პატივისცემას სტაფისა თუ პროგრამისგან იმ ინსტანციებში. + followers_count: მიმდევრების რაოდენობა + lock_link: თქვენი ანგარიშის ჩაკეტვა + purge: მიმდევრებიდან ამოშლა + success: + one: მიმდევრების სოფტ-ბლოკირების პროცესი ერთი დომენზე... + other: მიმდევრების სოფტ-ბლოკირების პროცესი %{count} დომენზე... + true_privacy_html: გთხოვთ გაითვალისწინეთ, ჭეშმარიტი კონფიდენციალურობა მიღწევადია მხოლოდ ენდ-თუ-ენდ შიფრაციით. + unlocked_warning_html: ყველას შეუძლია გამოგყვეთ, რომ უცბად იხილოს თქვენი სტატუსები. %{lock_link} რომ შეძლოთ განიხილოთ და უარყოთ მიმდევრები. + unlocked_warning_title: თქვენი ანგარიში არაა ჩაკეტილი + footer: + developers: დეველოპერები + more: მეტი… + resources: რესურსები + generic: + changes_saved_msg: ცვლილებები წარმატებით დამახსოვრდა! + save_changes: ცვლილებების შენახვა + validation_errors: + one: რაღაც ჯერ არაა მთლად კარგად! გთხოვთ განიხილოთ ქვემოთ მოცემული შეცდომები + other: რაღაც ჯერ არაა მთლად კარგად! გთხოვთ განიხილოთ ქვემოთ მოცემული %{count} შეცდომა + imports: + preface: შეგიძლიათ დააიმპორტოთ მონაცემები, რომლებიც დააექსპორტეთ სხვა სერვერიდან, მაგალითად ადამიანების სია, რომლებსაც მიჰყვებით ან ბლოკავთ. + success: თქვენი მონაცემები წარმატებით აიტვირთა და მათი პროცესირება მოხდება გარკვეულ დროში + types: + blocking: ბლოკირების სია + following: დადევნების სია + muting: გაჩუმების სია + upload: ატვირთვა + in_memoriam_html: მემორანდუმში. + invites: + delete: დეაქტივაცია + expired: ვადა გაუვიდა + expires_in: + '1800': 30 წუთში + '21600': 6 საათში + '3600': 1 საათში + '43200': 12 საათში + '604800': 1 კვირაში + '86400': 1 დღეში + expires_in_prompt: არასდროს + generate: გენერირება + invited_by: 'თქვენ მოგიწვიათ:' + max_uses: + one: 1 მოხმარება + other: "%{count} მოხმარება" + max_uses_prompt: ლიმიტის გარეშე + prompt: ამ ინსტანციაზე წვდომის მისაცემად, დააგენერირეთ და გააზიარეთ ბმულები სხვებთან + table: + expires_at: ვადა გასდის + uses: მოხმარება + title: მოიწვიეთ ხალხი + lists: + errors: + limit: მიაღწიეთ სიების მაქსიმალურ ოდენობას + media_attachments: + validations: + images_and_video: ვიდეოს დართვა სტატუსზე, რომელიც უკვე მოიცავს სურათებს, ვერ მოხერხდება + too_many: თან ვერ დაურთავთ 4 ფაილზე მეტს + migrations: + acct: username@domain ახალი ანგარიშის + currently_redirecting: 'თქვენი პროფილი გამართულია მოახდინოს გადამისამართება მისამართზე:' + proceed: შენახვა + updated_msg: თქვენი ანგარიშის მიგრაციის პარამეტრები წარმატეებით დამახსოვრდა! + moderation: + title: მოდერაცია + notification_mailer: + digest: + action: ყველა შეტყობინების ჩვენება + body: 'აქ მოკლე შინაარსია წერილების, რომლებიც გამოგეპარათ წინა სტუმრობის შემდეგ: %{since}' + mention: "%{name}-მა დაგასახელათ:" + new_followers_summary: + one: ასევე, არყოფნისას შეგეძინათ ერთი ახალი მიმდევარი! იეი! + other: ასევე, არყოფნისას შეგეძინათ %{count} ახალი მიმდევარი! შესანიშნავია! + subject: + one: "1 ახალი შეტყობინება თქვენი ბოლო სტუმრობის შემდეგ \U0001F418" + other: "%{count} ახალი შეტყობინება თქვენი ბოლო სტუმრობის შემდეგ \U0001F418" + title: თქვენს არყოფნაში... + favourite: + body: 'თქვენი სტატუსი ფავორიტი გახადა %{name}-მა:' + subject: "%{name}-მა თქვენი სტატუსი გახადა ფავორიტი" + title: ახალი ფავორიტი + follow: + body: "%{name} ახლა მოგყვებათ!" + subject: "%{name} ახლა მოგყვებათ" + title: ახალი მიმდევარი + follow_request: + action: დადევნების მოთხოვნების მენეჯმენტი + body: "%{name}-მა მოითხოვა გამოგყვეთ" + subject: 'მიმდევარი მოლოდინში: %{name}' + title: ახალი დადევნების მოთხოვნა + mention: + action: პასუხი + body: 'თქვენ %{name}-მა გასახელათ:' + subject: თქვენ გასახელათ %{name}-მა + title: ახალი სახელობა + reblog: + body: 'თქვენი სტატუსი გაზარდა %{name}-მა:' + subject: "%{name}-მა გაზარდა თქვენი სტატუსი" + title: ახალი ბუსტი + number: + human: + decimal_units: + format: "%n%u" + units: + billion: ბილ. + million: მილ. + quadrillion: კუად. + thousand: ათას. + trillion: ტრილ. + unit: '' + pagination: + newer: უფრო ახალი + next: შემდეგი + older: ძველი + prev: წინა + truncate: "…" + preferences: + languages: ენები + other: სხვა + publishing: გამოქვეყნება + web: ვები + remote_follow: + acct: შეიყვანეთ თქვენი username@domain საიდანაც გსურთ გაჰყვეთ + missing_resource: საჭირო გადამისამართების ურლ თქვენი ანგარიშისთვის ვერ მოიძებნა + no_account_html: არ გაქვთ ანგარიში? შეგიძლიათ დარეგისტრირდეთ აქ + proceed: გააგრძელეთ გასაყოლად + prompt: 'თქვენ გაჰყვებით:' + remote_unfollow: + error: შეცდომა + title: სათაური + unfollowed: დადევნების შეწყვეტა + sessions: + activity: ბოლო აქტივობა + browser: ბრაუზერი + browsers: + alipay: ალიფეი + blackberry: ბლექბერი + chrome: ქრომი + edge: მაიკროსოფთ ედჯი + electron: ელექტრონი + firefox: ფაირფოქსი + generic: ამოუცნობი ბრაუზერი + ie: ინტერნეტ ექფლორერი + micro_messenger: მიკრო-მესინჯერი + nokia: ნოკია ს40 ოვი ბრაუზერი + opera: ოპერა + otter: ოტერი + phantom_js: ფანტომჯეიესი + qq: ქქ ბრაუზერი + safari: საფარი + uc_browser: იუსიბიბრაუზერი + weibo: ვეიბო + current_session: მიმდინარე სესია + description: "%{browser} %{platform}-ზე" + explanation: ეს ვებ-ბრაუზერებია, რომლებიც ამჟამად აუტენტიფიცირებულ არიან თქვენს მასტოდონ ანგარიშთან. + ip: აი-პი + platforms: + adobe_air: ედობ ეარი + android: ანდროიდი + blackberry: ბლექბერი + chrome_os: ქრომო-ოსი + firefox_os: ფაირფოქს-ოსი + ios: აი-ოსი + linux: ლინუქსი + mac: მაკი + other: ამოუცნობი პლატფორმა + windows: ვინდოუსი + windows_mobile: ვინდოუს მობაილი + windows_phone: ვინდოუს ფოუნი + revoke: გაუქმება + revoke_success: სესია წარმატებით გაუქმდა + title: სესიები + settings: + authorized_apps: ავტორიზირებული აპლიკაციები + back: უკან მასტოდონისკენ + delete: ანგარიშის გაუქმება + development: დეველოპმენტი + edit_profile: პროფილის ცვლილება + export: მონაცემის ექსპორტი + followers: ავტორიზირებული მიმდევრები + import: იმპორტი + migrate: ანგარიშის მიგრაცია + notifications: შეტყობინებები + preferences: პრეფერენციები + settings: პარამეტრები + two_factor_authentication: მეორე-ფაქტორის აუტენტიფიკაცია + your_apps: თქვენი აპლიკაციები + statuses: + attached: + description: 'თან დართული: %{attached}' + image: + one: "%{count} სურათი" + other: "%{count} სურათები" + video: + one: "%{count} ვიდეო" + other: "%{count} ვიდეოები" + boosted_from_html: გაიზარდა %{acct_link}-იდან + content_warning: 'გაფრთხილება კონტენტზე: %{warning}' + disallowed_hashtags: + one: 'მოიცავდა აკრძალულ ჰეშტეგს: %{tags}' + other: 'მოიცავს აკრძალულ ჰეშტეგს: %{tags}' + language_detection: ავტომატურად დადგინდეს ენა + open_in_web: ვებში გახნსა + over_character_limit: ნიშნების ლიმიტი გადასცდა %{max}-ს + pin_errors: + limit: ტუტების მაქსიმალური რაოდენობა უკვე აპინეთ + ownership: სხვისი ტუტი ვერ აიპინება + private: არა-საჯარო ტუტი ვერ აიპინება + reblog: ბუსტი ვერ აიპინება + show_more: მეტის ჩვენება + title: '%{name}: "%{quote}"' + visibilities: + private: მხოლოდ-მიმდევრები + private_long: აჩვენე მხოლოდ მიმდევრებს + public: საჯარო + public_long: ხედავს ყველა + unlisted: ჩამოუთვლელი + unlisted_long: ხედავს ყველა, მაგრამ არ ჩანს საჯარო თაიმლაინებში + stream_entries: + pinned: აპინული ტუტი + reblogged: გაზრდილი + sensitive_content: მგრძნობიარე კონტენტი + terms: + body_html: | +

კონფიდენციალურობის პოლიტიკა

+

რა ინფორმაციას ვაგროვებთ?

+ + + +
+ +

რაში ვიყენებთ ინფორმაციას?

+ +

ნებისმიერი სხვა ინფორმაცია, რომელსაც ვაგროვებთ თქვენგან შესაძლოა გამოყენებულ იქნას შემდეგი გზებით:

+ + + +
+ +

როგორ ვიცავთ თქვენს ინფორმაციას?

+ +

მიღებული გვაქვს სხვადასხვა ზომა, შევინარჩუნოთ თქვენი პირადი ინფორმაციის უსაფრთხოება, რომელსაც აგზავნით, შეგყავთ ან კითხულობთ. ამ ყველაფერთან ერთად თქვენი ბრაუზერის სესია, ტრეფიკი თქვენს აპლიკაციასა და აპის შორის დაცულია სსლ-ით, თქვენი პაროლი იშიფრება ძლიერი ალგორითმით. შეგიძლიათ ჩართოთ მეორე-ფაქტორის აუტენტიფიკაცია, რათა გააღმაოთ თქვენი ანგარიშის თავდაცვა.

+ +
+ +

რა არის ჩვენი მონაცემის უარყოფის პოლიტიკა?

+ +

ჩვენ არ დავიშურებთ ძალისხმევას რომ:

+ + + +

შეგიძლიათ მოითხოვოთ და ჩამოტვირთოთ თქვენი კონტენტის არქივი, რომელიც მოიცავს თქვენს პოსტებს, მედია ფაილებს, პროფილის და დასათაურების სურათს.

+ +

შეგიძლიათ დაუბრუნებლად გააუქმოთ თქვენი ანგარიში ნებისმიერ დროს.

+ +
+ +

ვიყენებთ თუ არა ქუქის?

+ +

დიახ. ქუქიები წარმოადგენენ პატარა ფაილებს, რომელთაც, საიტი ან სერვის-პროვაიდერი, ათავსებს თქვენი კომპიუტერის მყარ დისკზე, ვებ-ბრაუზერის (თუ ნებას რთავთ) მეშვეობით. ქუქიები საშუალებას აძლევს საიტს ამოიცნონ თქვენი ბრაუზზერი და თუ გაქვთ რეგისტრირებული ანგარიში მისი ასოციაცია მოახდინონ თქვენს ანგარიშთან.

+ +

ჩვენ ვიყენებთ ქუქის, ვიცოდეთ და შევინახოთ თქვენი პრეფერენსიები სამომავლო სტუმრობებისთვის.

+ +
+ +

ვამჟღავნებთ თუ არა ინფორმაციას გარე მხარეებისთვის?

+ +

ჩვენ არ ვყიდით, ვვაჭრობთ ან გადაქვაქ თქვენთვის პირადად იდენტიფიცირებადი ინფორმაცია სხვა მხარეებისთვის. ეს არ მოიცავს სანდო მხარეებს, რომლებიც გვეხმარება საიტის ოპერირებაში, ჩვენი საქმიანობის ჩატარებაში, ან თქვენთვის მომსახურების გაწევაში, წინაპირობით კონფიდენციალურად შეინახონ თქვენი ინფორმაცია. ჩვენ შესაძლოა გამოვაქვეყნოთ თქვენი ინფორმაცია, რომელიც შესაბამისად შეიძლება ჩავთვალოთ კანონმდებლობასთან შეთავსებისთვის, აღვასრულოთ პოლიტიკა ან დავიცვათ ჩვენი ან სხვისი უფლებები, კუთვნილება ან უსაფრთხოება.

+ +

თქვენი საჯარო ინფორმაცია შესაძლოა ჩამოტვირთულ იქნას სხვა სერვერების მიერ ქსელში. თქვენი ღია და მიმდევრებზე გათვლილი პოსტები მიეწოდება სერვერებს სადაც თქვენი მიმდევრები მოღვაწეობენ, იმ შემთხვევაში თუ მიმღებები მომდინარეობენ სხვა სერვერიდან, პირდაპირი წერილები მიეწოდებათ მიმღებების სერვერებს.

+ +

როდესაც უფლებას მისცემთ აპლიკაციას გამოიყენოს თქვენი ანგარიში, უფლებებისგან გამომდინარე, მან შესაძლოა მოიპოვოს თქვენი საჯარო ინფორმაცია, თქვენი დადევნების სიები, თქვენი მიმდევრები, თქვენი სიები, ყველა პოსტი და თქვენი ფავორიტები. აპლიკაციები ვერასდროს იქონიებენ წვდომას თქვენი ელ-ფოსტის მისამართზე ან პაროლზე.

+ +
+ +

საიტის მოხმარებს ბავშვების მიერ

+ +

თუ ეს სერვერი მდებარეობს ეუ-ში ან ეეა-ში: ჩვენი საიტი, პროდუქტები და სერვისები მიმართულია ადამიანებისთვის, რომელთაც შეუსრულდათ 16 წელი. თუ თქვენი ასაკი 16 წელიწადზე ნაკლებია, ჯიდიფიარის (ზოგადი მონაცემების დაცვის რეგულაცია/a>) მოთხოვნის მიხედვით არ გამოიყენოთ ეს საიტი.

+ +

თუ ეს სერვერი მდებარეობს ა.შ.შ.-ში: ჩვენი საიტი პროდუქტი და სერვისები მიმართულია ადამიანებისთვის, რომელთაც შეუსრულდათ 13 წელი. თუ თქვენი ასაკი 13 წელიწადზე ნაკლებია, კოპპას (ბავშვთა ონლაინ კონფიდენციალურობის დაცვის აქტი) მოთხოვნების მიხედვით არ გამოიყენოთ ეს საიტი.

+ +

იურიდიული მოთხოვნილებები შეიძლება განსხვავდებოდეს, თუ ეს სერვერი იმყოფება სხვა იურისდიქციის ქვეშ.

+ +
+ +

ცვლილებები კონფიდენციალურობის პოლიტიკაში

+ +

თუ გადავწყვეტთ შევცვალოთ კონფიდენციალურობის პოლიტიკა, გამოვაქვეყნებთ ამ გვერდზე.

+ +

ეს დოკუმენტი არის ცც-ბაი-სა. ეს ბოლოს განახლდა 2018 წლის, 17 აგვისტოს.

+ +

საწყისად ადაპტირებულია გამჟღავნების კონფიდენციალური პოლიტიკისგან.

+ title: "%{instance} მომსახურების პირობები და კონფიდენციალურობის პოლიტიკა" + themes: + contrast: მაღალი კონტრასტი + default: მასტოდონი + mastodon-light: მასტოდონი (ღია) + time: + formats: + default: "%b %d, %Y, %H:%M" + month: "%b %Y" + two_factor_authentication: + code_hint: დასამოწმებლად შეიყვანეთ თქვენი აუტენტიფიკატორ აპლიკაციისგან გენერირებული კოდი + description_html: თუ ჩართავთ მეორე-ფაქტორის აუტენტიფიკაციას, შესვლისას აუცილებელი იქნება ფლობდეთ ტელეფონს, რომელიც დააგენერირებს შესვლის ტოკენებს. + disable: გათიშვა + enable: ჩართვა + enabled: მეორე-ფაქტორის აუტენტიფიკაცია ჩართულია + enabled_success: მეორე-ფაქტორის აუტენტიფიკაცია წარმატებით ჩაირთო + generate_recovery_codes: აღდგენის კოდების გენერაცია + instructions_html: "დაასკანირეთ ეს ქრ კოდი გუგლ აუტენტიფიკატორში ან მსგავს ტოტპ აპლიკაციაში თქვენს ტელეფონზე. ამიერიდან, ეს აპლიკაცია დააგენერირებს ტოკენებს მაშინ როდესაც დაგჭირდებათ ავტორიზაცია." + lost_recovery_codes: აღდგენის კოდები უფლებას გაძლევთ მიიღოთ ხელმეორე წვდომა თქვენი ანგარიშისადმი თუ დაკარგავთ ტელეფონს. თუ დაკარგეთ აღდგენის კოდები, მათ რეგენერაცია შეგიძლიათ აქ. ძველი აღდგენის კოდები აღარ იქნება ვალიდური. + manual_instructions: 'თუ ვერ ასკანირებთ ქრ კოდს და საჭიროებთ მის მექანიკურ რეჟიმში შეყვანას, აქ არის ჩვეულებრივი ტექსტური საიდუმლო:' + recovery_codes: გაუწიეთ აღდგენის კოდებს რეზერვაცია + recovery_codes_regenerated: აღგენის კოდების რეგენერაცია წარმატებით შესრულდა + recovery_instructions_html: თუ როდესმე დაკარგავთ წვდომას თქვენს ტელეფონთან, შეგიძლიათ ქვემოთ მოცემული აღდგენის კოდები გამოიყენოთ, რათა მოიპოვოთ ხელმეორე წვდომა თქვენი ანგარიშისადმი. იქონიეთ აღდგენის კოდები დაცულად. მაგალითისთვის, შეგიძლიათ ამობეჭდოთ და შეინახოთ სხვა საბუთებთან ერთად. + setup: დაყენება + wrong_code: შეყვანილი კოდი არ იყო სწორი! სწორია სერვერის და მოწყობილობის დრო? + user_mailer: + backup_ready: + explanation: თქვენ მოითხოვეთ თქვენი მასტოდონის ანგარიშის სრული რეზერვაცია. ის ახლა უკვე მზადაა გადმოსაწერად! + subject: თქვენი არქივი გადმოსაწერად მზადაა + title: არქივის მიღება + welcome: + edit_profile_action: პროფილის მოწყობა + edit_profile_step: შეგიძლიათ მოაწყოთ თქვენი პროფილი ავატარის ატვირთვით, დასათაურების სურათით, თქვენი დისპლეი სახელის შეცვლით და სხვა. თუ გსურთ გაუწიოთ ახალ მიმდევრებს რევიუ, სანამ რეალურად გამოგყვებიან, შეგიძლიათ ჩაკეტოთ თქვენი ანგარიში. + explanation: აქ რამდენიმე რჩევაა დასაწყისისთვის + final_action: დაიწყე პოსტვა + final_step: 'დაიწყე პოსტვა! თქვენი ღია წერილები შესაძლოა ნახონ სხვებმა მიმდევრების გარეშეც კი, მაგალითად თქვენს ლოკალურ თაიმლაინზე ან ჰეშტეგებში. შეგიძლიათ წარადგინოთ თქვენი თავი #introductions ჰეშტეგით.' + full_handle: თქვენი სრული სახელური + full_handle_hint: ეს არის ის რასაც ეტყვით თქვენს მეგობრებს, რათა მოგწერონ ან გამოგყვნენ სხვა ინსტანციიდან. + review_preferences_action: შეცვალეთ პრეფერენსიები + review_preferences_step: დარწმუნდით რომ აყენებთ თქვენს პრეფერენსიებს, მაგალითად რა ელ-ფოსტის წერილების მიღება გსურთ, ან კონფიდენციალურობის რა დონე გსურთ ჰქონდეთ თქვენს პოსტებს საწყისად. თუ არ გაღიზიანებთ მოძრაობა, შეგიძლიათ ჩართოთ გიფის ავტო-დაკვრა. + subject: კეთილი იყოს თქვენი მობრძანება მასტოდონში + tip_bridge_html: თუ მოდიხართ ტვიტერიდან, შეგიძლიათ იპოვოთ მეგობრები მასტოდონში ხიდის აპლიკაციით. თუმცა, ეს მუშაობს მხოლოდ მაშინ თუ მათაც მოიხმარეს ხიდის აპლიკაცია! + tip_federated_timeline: ფედერალური თაიმლაინი მასტოდონის ქსელის ცეცხლოვანი ხედია. ის მოიცავს მხოლოდ იმ ადამიანებს, რომელთაგანაც გამოიწერეს თქვენმა მეზობლებმა, ასე რომ ეს არაა სრული. + tip_following: თქვენ საწყისად მიჰყვებით თქვენი სერვერის ადმინისტრატორ(ებ)ს. უფრო საინტერესო ადამიანების მოსაძებნად იხილეთ ლოკალური და ფედერალური თაიმლაინები. + tip_local_timeline: ლოკალური თაიმლაინი ცეცხლოვანი ხედია ადამიანებისთვის %{instance}-ზე. ისინი არიან თქვენი უსიტყვო მეზობლები! + tip_mobile_webapp: თუ თქვენი მობილური ბრაუზერი გთავაზობთ მასტოდონის სახლის-ეკრანზე დამატებას, შეძლებთ ფუშ შეტყობინებების მიღებას. ეს მრავალმხრივ მოქმედებს როგორც მშობლიური აპლიკაცია! + tips: რჩევები + title: კეთილი იყოს თქვენი მობრძანება, %{name}! + users: + invalid_email: ელ-ფოსტის მისამართი არაა მართებული + invalid_otp_token: არასწორი მეორე ფაქტორის კოდი + otp_lost_help_html: თუ დაკარგეთ წვდომა ორივეზე, შესაძლოა დაუკავშირდეთ %{email}-ს + seamless_external_login: შესული ხართ გარე სერვისით, აქედან გამომდინარე პაროლი და ელ-ფოსტის მისამართი არაა ხელმისაწვდომი. + signed_in_as: 'შესული ხართ როგორც:' diff --git a/config/locales/simple_form.ka.yml b/config/locales/simple_form.ka.yml new file mode 100644 index 000000000..eb6d82bb2 --- /dev/null +++ b/config/locales/simple_form.ka.yml @@ -0,0 +1,99 @@ +--- +ka: + simple_form: + hints: + defaults: + autofollow: ადამიანები რომლებიც დარეგისტრირდებიან მოწვევით, ავტომატურად გამოგყვებიან + avatar: პნგ, გიფ ან ჯპგ. მაქს. %{size}. ზომა დაპატარავდება %{dimensions}პიქს.-ზე + bot: ეს ანგარიში უმთავრესად ასრულებს ავტომატურ მოქმედებებს და შესაძლოა არ იყოს მონიტორინგის ქვეშ + context: ერთ ან მრავალი კონტექსტი სადაც ფილტრი უნდა შესრულდეს + digest: იგზავნება მხოლოდ ხანგრძლივი უაქტივობის პერიოდის შემდეგ და არყოფნისას თუ მიიღეთ ერთი წერილი მაინც + display_name: + one: დარჩა ერთი ნიშანი + other: დარჩა %{count} ნიშანი + fields: პროფილზე ტაბულის სახით შესაძლოა საჩვენებლად გაგაჩნდეთ მაქს. 4 პუნქტი + header: პნგ, გიფ ან ჯპგ. მაქს. %{size}. ზომა დაპატარავდება %{dimensions}პიქს.-ზე + inbox_url: ურლ დააკოირეთ გამოყენებისთვის სასურველი რილეის წინა გვერდიდან + irreversible: გაფილტრული ტუტები გაუქმდება აღუდგენლად, იმ შემთხვევაშიც კი თუ ფილტრი სამომავლოდ გაუქმდება + locale: მომხმარებლის ინტერფეისის, ელ-ფოსტის წერილების და ფუშ შეტყობინებების ენა + locked: საჭიროებს თქვენ მიერ მიმდევრების ხელით დადასტურებას + note: + one: დარჩა ერთი ნიშანი + other: დარჩა %{count} ნიშანი + phrase: დამთხვევა მოხდება დიდი და პატარა ასოების ან კონტენტის გაფრთხილების გათვალისწინების გარეშე + scopes: რომელი აპიებისადმი ექნება აპლიკაციას ცვდომა. თუ არიჩევთ უმთავრეს ფარგლებს, არ დაგჭირდებათ ინდივიდუალურების ამორჩევა. + setting_default_language: თქვენი ტუტების ენა შეიძლება დადგინდეს ავტომატურად, მაგრამ ეს არაა ყოველთვის ზუსტი + setting_hide_network: ვის მიყვებით და ვინ მოგყვებათ არ გამოჩნდება აქ + setting_noindex: გავლენას ახდენს თქვენს ღია პროფილისა და სტატუსის გვერდებზე + setting_theme: გავლენას ახდენს თუ როგორ გამოიყურება მასტოდონი, როდესაც შესული ხართ რომელიმე მოწყობილობიდან. + whole_word: როდესაც სიტყვა ან ფრაზა მხოლოდ ალფა-ნუმერიკულია, ის დაფიქსირდება თუ ემთხვევა სრულ სიტყვას + imports: + data: ცსვ ფაილის ექსპორტი მოხდა მასტოდონის სხვა ინსტანციიდან + sessions: + otp: 'შეიყვანეთ მეორე ფაქტორის კოდი, რომელიც დააგერირა თქვენმა ტელეფონმა ან მოიხმარეთ შემდეგი აღდგენის კოდებიდან ერთ-ერთი:' + user: + chosen_languages: როდესაც მოინიშნება, ღია თაიმლაინზე გამოჩნდება ტუტები მხოლოდ არჩეულ ენებზე + labels: + account: + fields: + name: ლეიბლი + value: მოცულობა + defaults: + autofollow: მოიწვიეთ რომ გამოჰყვნენ თქვენს ანგარიშს + avatar: ავატარი + bot: ეს ბოტის ანგარიშია + chosen_languages: ენების ფილტრი + confirm_new_password: დაადასტურეთ ახალი პაროლი + confirm_password: დაადასტურეთ პაროლი + context: კონტექსტის ფილტრი + current_password: მიმდინარე პაროლი + data: მონაცემები + display_name: დისპლეის სახელი + email: ელ-ფოსტის მისამართი + expires_in: ვადის გასვლის დრო + fields: პროფილის მეტა-მონაცემი + header: დასათაურება + inbox_url: რილეი ინბოქსის ურლ + irreversible: გაუქმდეს დამალვის მაგივრად + locale: ინტერფეისის ენა + locked: ანგარიშის ჩაკეტვა + max_uses: მოხმარების მაქს. ოდენობა + new_password: ახალი პაროლი + note: ბიო. + otp_attempt: მეორე-ფაქტორის კოდი + password: პაროლი + phrase: სიტყვა ან ფრაზა + setting_auto_play_gif: ანიმაციური გიფების ავტო-დაკვრა + setting_boost_modal: ბუსტამე მოხდეს დამოწმება + setting_default_language: პოსტინგის ენა + setting_default_privacy: პოსტის კონფიდენციალურობა + setting_default_sensitive: ყოველთვის მოინიშნოს მედია მგრძნობიარედ + setting_delete_modal: ტუტის გაუქმებამდე გამოჩნდეს დადასტურების ფანჯარა + setting_display_sensitive_media: ყოველთვის გამოჩნდეს მგრძნობიარე მედია + setting_hide_network: თქვენი ქსელის დამალვა + setting_noindex: საძოები სისტემების ინდექსაციის შეჩერება + setting_reduce_motion: მოძრაობის შემცირება ანიმაციებში + setting_system_font_ui: მოხდეს სისტემის საწყისი ფონტის მოხმარება + setting_theme: საიტის თემა + setting_unfollow_modal: გამოჩნდეს დადასტურების ფანჯარა, სანამ შეყვეტთ ვინმეს დადევნებას + severity: სიმძიმე + type: იმპორტის სახეობა + username: მომხმარებლის სახელი + username_or_email: მომხმარებლის სახელი ან ელ-ფოსტა + whole_word: მთელი სიტყვა + interactions: + must_be_follower: დაიბლოკოს შეტყობინებები არა მიმდევრებისგან + must_be_following: დაიბლოკოს შეტყობინებები ადამიანებისგან ვისაც არ მიჰყვებით + must_be_following_dm: დაიბლოკოს პირადი წერილები ადამიანბისგან ვისაც არ მიჰყვებით + notification_emails: + digest: გამოიგზავნოს დაიჯესტ წერილები + favourite: გამოიგზავნოს წერილი როდესაც ვინმე ფავორიტად აქცევს თქვენს სტატუსს + follow: გამოიგზავნოს წერილი როდესაც ვინმე გამოგყვებათ + follow_request: გამოიგზავნოს წერილი როდესაც ვინმე მოგთხოვთ გაჰყვეთ + mention: გამოიგზავნოს წერილი როდესაც ვინმე გასახელებთ + reblog: გამოიგზავნოს წერილი როდესაც ვინმე გაზრდის თქვენს სტატუსს + 'no': არა + required: + mark: "*" + text: აუცილებელი + 'yes': კი From 78fa926ed560e6a9738144bec7e152fa42104139 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 18 Aug 2018 03:03:12 +0200 Subject: [PATCH 2/7] Add remote interaction dialog for toots (#8202) * Add remote interaction dialog for toots * Change AuthorizeFollow into AuthorizeInteraction, support statuses * Update brakeman.ignore * Adjust how interaction buttons are display on public pages * Fix tests --- .../authorize_follows_controller.rb | 66 ------- .../authorize_interactions_controller.rb | 66 +++++++ app/controllers/intents_controller.rb | 2 +- app/controllers/remote_follow_controller.rb | 1 + .../remote_interaction_controller.rb | 48 +++++ app/helpers/home_helper.rb | 10 + app/javascript/packs/public.js | 21 +- .../styles/mastodon/components.scss | 1 + .../styles/mastodon/stream_entries.scss | 5 + app/models/remote_follow.rb | 4 + app/serializers/webfinger_serializer.rb | 2 +- app/views/authorize_follows/show.html.haml | 17 -- .../_post_follow_actions.html.haml | 4 +- .../error.html.haml | 0 .../authorize_interactions/show.html.haml | 18 ++ .../success.html.haml | 6 +- app/views/layouts/modal.html.haml | 2 +- app/views/remote_interaction/new.html.haml | 17 ++ .../stream_entries/_detailed_status.html.haml | 15 +- .../stream_entries/_simple_status.html.haml | 10 +- app/views/stream_entries/show.html.haml | 2 +- app/views/well_known/webfinger/show.xml.ruby | 2 +- config/brakeman.ignore | 180 +++++++++++------- config/locales/en.yml | 5 +- config/routes.rb | 5 +- ...authorize_interactions_controller_spec.rb} | 31 +-- spec/controllers/intents_controller_spec.rb | 2 +- 27 files changed, 347 insertions(+), 195 deletions(-) delete mode 100644 app/controllers/authorize_follows_controller.rb create mode 100644 app/controllers/authorize_interactions_controller.rb create mode 100644 app/controllers/remote_interaction_controller.rb delete mode 100644 app/views/authorize_follows/show.html.haml rename app/views/{authorize_follows => authorize_interactions}/_post_follow_actions.html.haml (61%) rename app/views/{authorize_follows => authorize_interactions}/error.html.haml (100%) create mode 100644 app/views/authorize_interactions/show.html.haml rename app/views/{authorize_follows => authorize_interactions}/success.html.haml (60%) create mode 100644 app/views/remote_interaction/new.html.haml rename spec/controllers/{authorize_follows_controller_spec.rb => authorize_interactions_controller_spec.rb} (71%) diff --git a/app/controllers/authorize_follows_controller.rb b/app/controllers/authorize_follows_controller.rb deleted file mode 100644 index 775d5f23f..000000000 --- a/app/controllers/authorize_follows_controller.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -class AuthorizeFollowsController < ApplicationController - layout 'modal' - - before_action :authenticate_user! - before_action :set_body_classes - - def show - @account = located_account || render(:error) - end - - def create - @account = follow_attempt.try(:target_account) - - if @account.nil? - render :error - else - render :success - end - rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError - render :error - end - - private - - def follow_attempt - FollowService.new.call(current_account, acct_without_prefix) - end - - def located_account - if acct_param_is_url? - account_from_remote_fetch - else - account_from_remote_follow - end - end - - def account_from_remote_fetch - FetchRemoteAccountService.new.call(acct_without_prefix) - end - - def account_from_remote_follow - ResolveAccountService.new.call(acct_without_prefix) - end - - def acct_param_is_url? - parsed_uri.path && %w(http https).include?(parsed_uri.scheme) - end - - def parsed_uri - Addressable::URI.parse(acct_without_prefix).normalize - end - - def acct_without_prefix - acct_params.gsub(/\Aacct:/, '') - end - - def acct_params - params.fetch(:acct, '') - end - - def set_body_classes - @body_classes = 'modal-layout' - end -end diff --git a/app/controllers/authorize_interactions_controller.rb b/app/controllers/authorize_interactions_controller.rb new file mode 100644 index 000000000..e27366ea3 --- /dev/null +++ b/app/controllers/authorize_interactions_controller.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +class AuthorizeInteractionsController < ApplicationController + include Authorization + + layout 'modal' + + before_action :authenticate_user! + before_action :set_body_classes + before_action :set_resource + + def show + if @resource.is_a?(Account) + render :show + elsif @resource.is_a?(Status) + redirect_to web_url("statuses/#{@resource.id}") + else + render :error + end + end + + def create + if @resource.is_a?(Account) && FollowService.new.call(current_account, @resource) + render :success + else + render :error + end + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError + render :error + end + + private + + def set_resource + @resource = located_resource || render(:error) + authorize(@resource, :show?) if @resource.is_a?(Status) + end + + def located_resource + if uri_param_is_url? + ResolveURLService.new.call(uri_param) + else + account_from_remote_follow + end + end + + def account_from_remote_follow + ResolveAccountService.new.call(uri_param) + end + + def uri_param_is_url? + parsed_uri.path && %w(http https).include?(parsed_uri.scheme) + end + + def parsed_uri + Addressable::URI.parse(uri_param).normalize + end + + def uri_param + params[:uri] || params.fetch(:acct, '').gsub(/\Aacct:/, '') + end + + def set_body_classes + @body_classes = 'modal-layout' + end +end diff --git a/app/controllers/intents_controller.rb b/app/controllers/intents_controller.rb index 56129d69a..9f41cf48a 100644 --- a/app/controllers/intents_controller.rb +++ b/app/controllers/intents_controller.rb @@ -8,7 +8,7 @@ class IntentsController < ApplicationController if uri.scheme == 'web+mastodon' case uri.host when 'follow' - return redirect_to authorize_follow_path(acct: uri.query_values['uri'].gsub(/\Aacct:/, '')) + return redirect_to authorize_interaction_path(uri: uri.query_values['uri'].gsub(/\Aacct:/, '')) when 'share' return redirect_to share_path(text: uri.query_values['text']) end diff --git a/app/controllers/remote_follow_controller.rb b/app/controllers/remote_follow_controller.rb index cd61fd763..8ba331cd1 100644 --- a/app/controllers/remote_follow_controller.rb +++ b/app/controllers/remote_follow_controller.rb @@ -42,5 +42,6 @@ class RemoteFollowController < ApplicationController def set_body_classes @body_classes = 'modal-layout' + @hide_header = true end end diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb new file mode 100644 index 000000000..6299a1e13 --- /dev/null +++ b/app/controllers/remote_interaction_controller.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class RemoteInteractionController < ApplicationController + include Authorization + + layout 'modal' + + before_action :set_status + before_action :set_body_classes + + def new + @remote_follow = RemoteFollow.new(session_params) + end + + def create + @remote_follow = RemoteFollow.new(resource_params) + + if @remote_follow.valid? + session[:remote_follow] = @remote_follow.acct + redirect_to @remote_follow.interact_address_for(@status) + else + render :new + end + end + + private + + def resource_params + params.require(:remote_follow).permit(:acct) + end + + def session_params + { acct: session[:remote_follow] } + end + + def set_status + @status = Status.find(params[:id]) + authorize @status, :show? + rescue Mastodon::NotPermittedError + # Reraise in order to get a 404 + raise ActiveRecord::RecordNotFound + end + + def set_body_classes + @body_classes = 'modal-layout' + @hide_header = true + end +end diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index 8449f6c8a..f5b501235 100644 --- a/app/helpers/home_helper.rb +++ b/app/helpers/home_helper.rb @@ -38,4 +38,14 @@ module HomeHelper end end end + + def obscured_counter(count) + if count <= 0 + 0 + elsif count == 1 + 1 + else + '1+' + end + end end diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 6b47eecf9..dc18da853 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -67,13 +67,6 @@ function main() { }, datetime, now, datetime.getFullYear()); }); - [].forEach.call(document.querySelectorAll('.modal-button'), (content) => { - content.addEventListener('click', (e) => { - e.preventDefault(); - window.open(e.target.href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); - }); - }); - const reactComponents = document.querySelectorAll('[data-component]'); if (reactComponents.length > 0) { import(/* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container') @@ -119,6 +112,20 @@ function main() { return false; }); + delegate(document, '.modal-button', 'click', e => { + e.preventDefault(); + + let href; + + if (e.target.nodeName !== 'A') { + href = e.target.parentNode.href; + } else { + href = e.target.href; + } + + window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); + }); + delegate(document, '#account_display_name', 'input', ({ target }) => { const nameCounter = document.querySelector('.name-counter'); const name = document.querySelector('.card .display-name strong'); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 931f1aa0d..cfd8e5ad4 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -628,6 +628,7 @@ overflow: hidden; white-space: pre-wrap; padding-top: 2px; + color: $primary-text-color; &:focus { outline: 0; diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss index 9e2aa720c..03bbd84db 100644 --- a/app/javascript/styles/mastodon/stream_entries.scss +++ b/app/javascript/styles/mastodon/stream_entries.scss @@ -3,6 +3,7 @@ border-radius: 4px; overflow: hidden; margin-bottom: 10px; + text-align: left; @media screen and (max-width: $no-gap-breakpoint) { margin-bottom: 0; @@ -63,6 +64,10 @@ } } } + + &--highlighted .entry { + background: lighten($ui-base-color, 8%); + } } .button.logo-button { diff --git a/app/models/remote_follow.rb b/app/models/remote_follow.rb index 070144e2d..2537de36c 100644 --- a/app/models/remote_follow.rb +++ b/app/models/remote_follow.rb @@ -22,6 +22,10 @@ class RemoteFollow addressable_template.expand(uri: account.local_username_and_domain).to_s end + def interact_address_for(status) + addressable_template.expand(uri: ActivityPub::TagManager.instance.uri_for(status)).to_s + end + private def populate_template diff --git a/app/serializers/webfinger_serializer.rb b/app/serializers/webfinger_serializer.rb index f80d12c02..8c0b07702 100644 --- a/app/serializers/webfinger_serializer.rb +++ b/app/serializers/webfinger_serializer.rb @@ -20,7 +20,7 @@ class WebfingerSerializer < ActiveModel::Serializer { rel: 'self', type: 'application/activity+json', href: account_url(object) }, { rel: 'salmon', href: api_salmon_url(object.id) }, { rel: 'magic-public-key', href: "data:application/magic-public-key,#{object.magic_key}" }, - { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_follow_url}?acct={uri}" }, + { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" }, ] end end diff --git a/app/views/authorize_follows/show.html.haml b/app/views/authorize_follows/show.html.haml deleted file mode 100644 index 90e65b34f..000000000 --- a/app/views/authorize_follows/show.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -- content_for :page_title do - = t('authorize_follow.title', acct: @account.acct) - -.form-container - .follow-prompt - = render 'application/card', account: @account - - - if current_account.following?(@account) - .flash-message - %strong - = t('authorize_follow.already_following') - = render 'post_follow_actions' - - - else - = form_tag authorize_follow_path, method: :post, class: 'simple_form' do - = hidden_field_tag :acct, @account.acct - = button_tag t('authorize_follow.follow'), type: :submit diff --git a/app/views/authorize_follows/_post_follow_actions.html.haml b/app/views/authorize_interactions/_post_follow_actions.html.haml similarity index 61% rename from app/views/authorize_follows/_post_follow_actions.html.haml rename to app/views/authorize_interactions/_post_follow_actions.html.haml index 2a9c062e9..561c60137 100644 --- a/app/views/authorize_follows/_post_follow_actions.html.haml +++ b/app/views/authorize_interactions/_post_follow_actions.html.haml @@ -1,4 +1,4 @@ .post-follow-actions - %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@account.id}"), class: 'button button--block' - %div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@account), class: 'button button--block' + %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@resource.id}"), class: 'button button--block' + %div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@resource), class: 'button button--block' %div= t('authorize_follow.post_follow.close') diff --git a/app/views/authorize_follows/error.html.haml b/app/views/authorize_interactions/error.html.haml similarity index 100% rename from app/views/authorize_follows/error.html.haml rename to app/views/authorize_interactions/error.html.haml diff --git a/app/views/authorize_interactions/show.html.haml b/app/views/authorize_interactions/show.html.haml new file mode 100644 index 000000000..7ca9b98c1 --- /dev/null +++ b/app/views/authorize_interactions/show.html.haml @@ -0,0 +1,18 @@ +- content_for :page_title do + = t('authorize_follow.title', acct: @resource.acct) + +.form-container + .follow-prompt + = render 'application/card', account: @resource + + - if current_account.following?(@resource) + .flash-message + %strong + = t('authorize_follow.already_following') + + = render 'post_follow_actions' + - else + = form_tag authorize_interaction_path, method: :post, class: 'simple_form' do + = hidden_field_tag :action, :follow + = hidden_field_tag :acct, @resource.acct + = button_tag t('authorize_follow.follow'), type: :submit diff --git a/app/views/authorize_follows/success.html.haml b/app/views/authorize_interactions/success.html.haml similarity index 60% rename from app/views/authorize_follows/success.html.haml rename to app/views/authorize_interactions/success.html.haml index cf9cb50ea..47fd09767 100644 --- a/app/views/authorize_follows/success.html.haml +++ b/app/views/authorize_interactions/success.html.haml @@ -1,13 +1,13 @@ - content_for :page_title do - = t('authorize_follow.title', acct: @account.acct) + = t('authorize_follow.title', acct: @resource.acct) .form-container .follow-prompt - - if @account.locked? + - if @resource.locked? %h2= t('authorize_follow.follow_request') - else %h2= t('authorize_follow.following') - = render 'application/card', account: @account + = render 'application/card', account: @resource = render 'post_follow_actions' diff --git a/app/views/layouts/modal.html.haml b/app/views/layouts/modal.html.haml index 325b4ec72..b73068459 100644 --- a/app/views/layouts/modal.html.haml +++ b/app/views/layouts/modal.html.haml @@ -2,7 +2,7 @@ = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous' - content_for :content do - - if user_signed_in? + - if user_signed_in? && !@hide_header .account-header .avatar= image_tag current_account.avatar.url(:original) .name diff --git a/app/views/remote_interaction/new.html.haml b/app/views/remote_interaction/new.html.haml new file mode 100644 index 000000000..7357546b6 --- /dev/null +++ b/app/views/remote_interaction/new.html.haml @@ -0,0 +1,17 @@ +.form-container + .follow-prompt + %h2= t('remote_interaction.prompt') + + .public-layout + .activity-stream.activity-stream--highlighted + = render 'stream_entries/status', status: @status + + = simple_form_for @remote_follow, as: :remote_follow, url: remote_interaction_path(@status) do |f| + = render 'shared/error_messages', object: @remote_follow + + = f.input :acct, placeholder: t('remote_follow.acct'), input_html: { autocapitalize: 'none', autocorrect: 'off' } + + .actions + = f.button :button, t('remote_interaction.proceed'), type: :submit + + %p.hint.subtle-hint= t('remote_follow.no_account_html', sign_up_path: open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started') diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index aa160b979..a7c767816 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -39,6 +39,11 @@ - else = link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener' · + = link_to remote_interaction_path(status), class: 'modal-button detailed-status__link' do + = fa_icon('reply') + %span.detailed-status__reblogs>= number_to_human status.replies_count, strip_insignificant_zeros: true + = " " + · - if status.direct_visibility? %span.detailed-status__link< = fa_icon('envelope') @@ -46,13 +51,15 @@ %span.detailed-status__link< = fa_icon('lock') - else - %span.detailed-status__link< + = link_to remote_interaction_path(status), class: 'modal-button detailed-status__link' do = fa_icon('retweet') - %span.detailed-status__reblogs= number_to_human status.reblogs_count, strip_insignificant_zeros: true + %span.detailed-status__reblogs>= number_to_human status.reblogs_count, strip_insignificant_zeros: true + = " " · - %span.detailed-status__link< + = link_to remote_interaction_path(status), class: 'modal-button detailed-status__link' do = fa_icon('star') - %span.detailed-status__favorites= number_to_human status.favourites_count, strip_insignificant_zeros: true + %span.detailed-status__favorites>= number_to_human status.favourites_count, strip_insignificant_zeros: true + = " " - if user_signed_in? · diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml index 676d367ca..ec8b69bb6 100644 --- a/app/views/stream_entries/_simple_status.html.haml +++ b/app/views/stream_entries/_simple_status.html.haml @@ -29,14 +29,16 @@ = react_component :media_gallery, height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } .status__action-bar - .status__action-bar-button.static-icon-button< + .status__action-bar__counter + = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do + = fa_icon 'reply fw' + .status__action-bar__counter__label= obscured_counter status.replies_count + = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do - if status.public_visibility? || status.unlisted_visibility? = fa_icon 'retweet fw' - %span.detailed-status__reblogs= number_to_human status.reblogs_count, strip_insignificant_zeros: true - elsif status.private_visibility? = fa_icon 'lock fw' - else = fa_icon 'envelope fw' - .status__action-bar-button.static-icon-button< + = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do = fa_icon 'star fw' - %span.detailed-status__favorites= number_to_human status.favourites_count, strip_insignificant_zeros: true diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml index 9da6245dc..2edc155bf 100644 --- a/app/views/stream_entries/show.html.haml +++ b/app/views/stream_entries/show.html.haml @@ -19,7 +19,7 @@ .grid .column-0 - .activity-stream.activity-stream-headless.h-entry + .activity-stream.h-entry = render partial: "stream_entries/#{@type}", locals: { @type.to_sym => @stream_entry.activity, include_threads: true } .column-1 = render 'application/sidebar' diff --git a/app/views/well_known/webfinger/show.xml.ruby b/app/views/well_known/webfinger/show.xml.ruby index 4352a24e9..968c8c138 100644 --- a/app/views/well_known/webfinger/show.xml.ruby +++ b/app/views/well_known/webfinger/show.xml.ruby @@ -37,7 +37,7 @@ doc << Ox::Element.new('XRD').tap do |xrd| xrd << Ox::Element.new('Link').tap do |link| link['rel'] = 'http://ostatus.org/schema/1.0/subscribe' - link['template'] = "#{authorize_follow_url}?acct={uri}" + link['template'] = "#{authorize_interaction_url}?acct={uri}" end end diff --git a/config/brakeman.ignore b/config/brakeman.ignore index e8956639c..40fef7283 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -1,5 +1,25 @@ { "ignored_warnings": [ + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "04dbbc249b989db2e0119bbb0f59c9818e12889d2b97c529cdc0b1526002ba4b", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/report.rb", + "line": 86, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "Admin::ActionLog.from(\"(#{[Admin::ActionLog.where(:target_type => \"Report\", :target_id => id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Account\", :target_id => target_account_id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Status\", :target_id => status_ids, :created_at => ((created_at..updated_at))).unscope(:order)].map do\n \"(#{query.to_sql})\"\n end.join(\" UNION ALL \")}) AS admin_action_logs\")", + "render_path": null, + "location": { + "type": "method", + "class": "Report", + "method": "history" + }, + "user_input": "Admin::ActionLog.where(:target_type => \"Status\", :target_id => status_ids, :created_at => ((created_at..updated_at))).unscope(:order)", + "confidence": "High", + "note": "" + }, { "warning_type": "Cross-Site Scripting", "warning_code": 4, @@ -7,8 +27,8 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 147, - "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "line": 167, + "link": "https://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).inbox_url, Account.find(params[:id]).inbox_url)", "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { @@ -26,8 +46,8 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 153, - "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "line": 173, + "link": "https://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).shared_inbox_url, Account.find(params[:id]).shared_inbox_url)", "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { @@ -45,8 +65,8 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 57, - "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "line": 75, + "link": "https://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).url, Account.find(params[:id]).url)", "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { @@ -57,6 +77,26 @@ "confidence": "Weak", "note": "" }, + { + "warning_type": "Mass Assignment", + "warning_code": 105, + "fingerprint": "28d81cc22580ef76e912b077b245f353499aa27b3826476667224c00227af2a9", + "check_name": "PermitAttributes", + "message": "Potentially dangerous key allowed for mass assignment", + "file": "app/controllers/admin/reports_controller.rb", + "line": 86, + "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", + "code": "params.permit(:account_id, :resolved, :target_account_id)", + "render_path": null, + "location": { + "type": "method", + "class": "Admin::ReportsController", + "method": "filter_params" + }, + "user_input": ":account_id", + "confidence": "High", + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -65,9 +105,9 @@ "message": "Render path contains parameter value", "file": "app/views/stream_entries/embed.html.haml", "line": 3, - "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => \"stream_entries/#{Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase}\", { Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase.to_sym => Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity, :centered => true })", - "render_path": [{"type":"controller","class":"StatusesController","method":"embed","line":45,"file":"app/controllers/statuses_controller.rb"}], + "render_path": [{"type":"controller","class":"StatusesController","method":"embed","line":58,"file":"app/controllers/statuses_controller.rb"}], "location": { "type": "template", "template": "stream_entries/embed" @@ -83,8 +123,8 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/admin/action_logs/index.html.haml", - "line": 5, - "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "line": 4, + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => Admin::ActionLog.page(params[:page]), {})", "render_path": [{"type":"controller","class":"Admin::ActionLogsController","method":"index","line":7,"file":"app/controllers/admin/action_logs_controller.rb"}], "location": { @@ -95,6 +135,26 @@ "confidence": "Weak", "note": "" }, + { + "warning_type": "Redirect", + "warning_code": 18, + "fingerprint": "5fad11cd67f905fab9b1d5739d01384a1748ebe78c5af5ac31518201925265a7", + "check_name": "Redirect", + "message": "Possible unprotected redirect", + "file": "app/controllers/remote_interaction_controller.rb", + "line": 20, + "link": "https://brakemanscanner.org/docs/warning_types/redirect/", + "code": "redirect_to(RemoteFollow.new(resource_params).interact_address_for(Status.find(params[:id])))", + "render_path": null, + "location": { + "type": "method", + "class": "RemoteInteractionController", + "method": "create" + }, + "user_input": "RemoteFollow.new(resource_params).interact_address_for(Status.find(params[:id]))", + "confidence": "High", + "note": "" + }, { "warning_type": "Cross-Site Scripting", "warning_code": 4, @@ -102,8 +162,8 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 156, - "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "line": 176, + "link": "https://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).followers_url, Account.find(params[:id]).followers_url)", "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { @@ -121,8 +181,8 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 130, - "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "line": 149, + "link": "https://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).salmon_url, Account.find(params[:id]).salmon_url)", "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { @@ -141,7 +201,7 @@ "message": "Render path contains parameter value", "file": "app/views/admin/custom_emojis/index.html.haml", "line": 45, - "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]), {})", "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":11,"file":"app/controllers/admin/custom_emojis_controller.rb"}], "location": { @@ -160,7 +220,7 @@ "message": "Possible SQL injection", "file": "lib/mastodon/snowflake.rb", "line": 87, - "link": "http://brakemanscanner.org/docs/warning_types/sql_injection/", + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "connection.execute(\" CREATE OR REPLACE FUNCTION timestamp_id(table_name text)\\n RETURNS bigint AS\\n $$\\n DECLARE\\n time_part bigint;\\n sequence_base bigint;\\n tail bigint;\\n BEGIN\\n time_part := (\\n -- Get the time in milliseconds\\n ((date_part('epoch', now()) * 1000))::bigint\\n -- And shift it over two bytes\\n << 16);\\n\\n sequence_base := (\\n 'x' ||\\n -- Take the first two bytes (four hex characters)\\n substr(\\n -- Of the MD5 hash of the data we documented\\n md5(table_name ||\\n '#{SecureRandom.hex(16)}' ||\\n time_part::text\\n ),\\n 1, 4\\n )\\n -- And turn it into a bigint\\n )::bit(16)::bigint;\\n\\n -- Finally, add our sequence number to our base, and chop\\n -- it to the last two bytes\\n tail := (\\n (sequence_base + nextval(table_name || '_id_seq'))\\n & 65535);\\n\\n -- Return the time part and the sequence part. OR appears\\n -- faster here than addition, but they're equivalent:\\n -- time_part has no trailing two bytes, and tail is only\\n -- the last two bytes.\\n RETURN time_part | tail;\\n END\\n $$ LANGUAGE plpgsql VOLATILE;\\n\")", "render_path": null, "location": { @@ -180,7 +240,7 @@ "message": "Render path contains parameter value", "file": "app/views/admin/accounts/index.html.haml", "line": 67, - "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => filtered_accounts.page(params[:page]), {})", "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"index","line":12,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { @@ -191,25 +251,6 @@ "confidence": "Weak", "note": "" }, - { - "warning_type": "Cross-Site Request Forgery", - "warning_code": 7, - "fingerprint": "ab491f72606337a348482d006eb67a3b1616685fd48644d5ac909bbcd62a5000", - "check_name": "ForgerySetting", - "message": "'protect_from_forgery' should be called in WellKnown::HostMetaController", - "file": "app/controllers/well_known/host_meta_controller.rb", - "line": 4, - "link": "http://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/", - "code": null, - "render_path": null, - "location": { - "type": "controller", - "controller": "WellKnown::HostMetaController" - }, - "user_input": null, - "confidence": "High", - "note": "" - }, { "warning_type": "Redirect", "warning_code": 18, @@ -218,7 +259,7 @@ "message": "Possible unprotected redirect", "file": "app/controllers/media_controller.rb", "line": 10, - "link": "http://brakemanscanner.org/docs/warning_types/redirect/", + "link": "https://brakemanscanner.org/docs/warning_types/redirect/", "code": "redirect_to(MediaAttachment.attached.find_by!(:shortcode => ((params[:id] or params[:medium_id]))).file.url(:original))", "render_path": null, "location": { @@ -237,8 +278,8 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 119, - "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "line": 138, + "link": "https://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).remote_url, Account.find(params[:id]).remote_url)", "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { @@ -256,8 +297,8 @@ "check_name": "Redirect", "message": "Possible unprotected redirect", "file": "app/controllers/remote_follow_controller.rb", - "line": 18, - "link": "http://brakemanscanner.org/docs/warning_types/redirect/", + "line": 19, + "link": "https://brakemanscanner.org/docs/warning_types/redirect/", "code": "redirect_to(RemoteFollow.new(resource_params).subscribe_address_for(Account.find_local!(params[:account_username])))", "render_path": null, "location": { @@ -276,8 +317,8 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/admin/reports/index.html.haml", - "line": 25, - "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "line": 22, + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => filtered_reports.page(params[:page]), {})", "render_path": [{"type":"controller","class":"Admin::ReportsController","method":"index","line":10,"file":"app/controllers/admin/reports_controller.rb"}], "location": { @@ -288,25 +329,6 @@ "confidence": "Weak", "note": "" }, - { - "warning_type": "Cross-Site Request Forgery", - "warning_code": 7, - "fingerprint": "d4278f04e807ec58a23925f8ab31fad5e84692f2fb9f2f57e7931aff05d57cf8", - "check_name": "ForgerySetting", - "message": "'protect_from_forgery' should be called in WellKnown::WebfingerController", - "file": "app/controllers/well_known/webfinger_controller.rb", - "line": 4, - "link": "http://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/", - "code": null, - "render_path": null, - "location": { - "type": "controller", - "controller": "WellKnown::WebfingerController" - }, - "user_input": null, - "confidence": "High", - "note": "" - }, { "warning_type": "Cross-Site Scripting", "warning_code": 4, @@ -314,8 +336,8 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 150, - "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "line": 170, + "link": "https://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).outbox_url, Account.find(params[:id]).outbox_url)", "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { @@ -326,6 +348,26 @@ "confidence": "Weak", "note": "" }, + { + "warning_type": "Mass Assignment", + "warning_code": 105, + "fingerprint": "e867661b2c9812bc8b75a5df12b28e2a53ab97015de0638b4e732fe442561b28", + "check_name": "PermitAttributes", + "message": "Potentially dangerous key allowed for mass assignment", + "file": "app/controllers/api/v1/reports_controller.rb", + "line": 42, + "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", + "code": "params.permit(:account_id, :comment, :forward, :status_ids => ([]))", + "render_path": null, + "location": { + "type": "method", + "class": "Api::V1::ReportsController", + "method": "report_params" + }, + "user_input": ":account_id", + "confidence": "High", + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -333,10 +375,10 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/stream_entries/show.html.haml", - "line": 24, - "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "line": 23, + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(partial => \"stream_entries/#{Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase}\", { :locals => ({ Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase.to_sym => Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity, :include_threads => true }) })", - "render_path": [{"type":"controller","class":"StatusesController","method":"show","line":22,"file":"app/controllers/statuses_controller.rb"}], + "render_path": [{"type":"controller","class":"StatusesController","method":"show","line":30,"file":"app/controllers/statuses_controller.rb"}], "location": { "type": "template", "template": "stream_entries/show" @@ -346,6 +388,6 @@ "note": "" } ], - "updated": "2018-02-16 06:42:53 +0100", - "brakeman_version": "4.0.1" + "updated": "2018-08-18 00:49:25 +0200", + "brakeman_version": "4.2.1" } diff --git a/config/locales/en.yml b/config/locales/en.yml index cc24a02cf..90086103c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -655,11 +655,14 @@ en: publishing: Publishing web: Web remote_follow: - acct: Enter your username@domain you want to follow from + acct: Enter your username@domain you want to act from missing_resource: Could not find the required redirect URL for your account no_account_html: Don't have an account? You can sign up here proceed: Proceed to follow prompt: 'You are going to follow:' + remote_interaction: + proceed: Proceed to interact + prompt: 'You want to interact with this toot:' remote_unfollow: error: Error title: Title diff --git a/config/routes.rb b/config/routes.rb index 2983011d2..fd3b5fe4b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -74,6 +74,9 @@ Rails.application.routes.draw do get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status + get '/interact/:id', to: 'remote_interaction#new', as: :remote_interaction + post '/interact/:id', to: 'remote_interaction#create' + namespace :settings do resource :profile, only: [:show, :update] resource :preferences, only: [:show, :update] @@ -120,7 +123,7 @@ Rails.application.routes.draw do # Remote follow resource :remote_unfollow, only: [:create] - resource :authorize_follow, only: [:show, :create] + resource :authorize_interaction, only: [:show, :create] resource :share, only: [:show, :create] namespace :admin do diff --git a/spec/controllers/authorize_follows_controller_spec.rb b/spec/controllers/authorize_interactions_controller_spec.rb similarity index 71% rename from spec/controllers/authorize_follows_controller_spec.rb rename to spec/controllers/authorize_interactions_controller_spec.rb index 52971c724..81fd9ceb7 100644 --- a/spec/controllers/authorize_follows_controller_spec.rb +++ b/spec/controllers/authorize_interactions_controller_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -describe AuthorizeFollowsController do +describe AuthorizeInteractionsController do render_views describe 'GET #show' do @@ -39,19 +39,19 @@ describe AuthorizeFollowsController do expect(service).to have_received(:call).with('missing@hostname') end - it 'sets account from url' do + it 'sets resource from url' do account = Account.new service = double - allow(FetchRemoteAccountService).to receive(:new).and_return(service) + allow(ResolveURLService).to receive(:new).and_return(service) allow(service).to receive(:call).with('http://example.com').and_return(account) get :show, params: { acct: 'http://example.com' } expect(response).to have_http_status(200) - expect(assigns(:account)).to eq account + expect(assigns(:resource)).to eq account end - it 'sets account from acct uri' do + it 'sets resource from acct uri' do account = Account.new service = double allow(ResolveAccountService).to receive(:new).and_return(service) @@ -60,7 +60,7 @@ describe AuthorizeFollowsController do get :show, params: { acct: 'acct:found@hostname' } expect(response).to have_http_status(200) - expect(assigns(:account)).to eq account + expect(assigns(:resource)).to eq account end end end @@ -75,8 +75,8 @@ describe AuthorizeFollowsController do end describe 'when signed in' do - let(:user) { Fabricate(:user) } - let(:account) { Fabricate(:account, user: user) } + let!(:user) { Fabricate(:user) } + let!(:account) { user.account } before do sign_in(user) @@ -84,25 +84,26 @@ describe AuthorizeFollowsController do it 'shows error when account not found' do service = double - allow(FollowService).to receive(:new).and_return(service) - allow(service).to receive(:call).with(account, 'user@hostname').and_return(nil) + + allow(ResolveAccountService).to receive(:new).and_return(service) + allow(service).to receive(:call).with('user@hostname').and_return(nil) post :create, params: { acct: 'acct:user@hostname' } - expect(service).to have_received(:call).with(account, 'user@hostname') expect(response).to render_template(:error) end it 'follows account when found' do target_account = Fabricate(:account) - result_account = double(target_account: target_account) service = double - allow(FollowService).to receive(:new).and_return(service) - allow(service).to receive(:call).with(account, 'user@hostname').and_return(result_account) + + allow(ResolveAccountService).to receive(:new).and_return(service) + allow(service).to receive(:call).with('user@hostname').and_return(target_account) post :create, params: { acct: 'acct:user@hostname' } - expect(service).to have_received(:call).with(account, 'user@hostname') + expect(service).to have_received(:call).with('user@hostname') + expect(account.following?(target_account)).to be true expect(response).to render_template(:success) end end diff --git a/spec/controllers/intents_controller_spec.rb b/spec/controllers/intents_controller_spec.rb index 3dde7f835..ddfd5ea36 100644 --- a/spec/controllers/intents_controller_spec.rb +++ b/spec/controllers/intents_controller_spec.rb @@ -13,7 +13,7 @@ RSpec.describe IntentsController, type: :controller do context 'when host is follow' do let(:uri) { 'web+mastodon://follow?uri=test' } - it { is_expected.to redirect_to authorize_follow_path(acct: 'test') } + it { is_expected.to redirect_to authorize_interaction_path(uri: 'test') } end context 'when host is share' do From d010816ba876bf4f749749e9d7432a843e8a1ff9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 18 Aug 2018 03:03:23 +0200 Subject: [PATCH 3/7] Fix error when trying to update counters for statuses that are gone (#8251) --- app/models/favourite.rb | 4 ++-- app/models/status.rb | 10 ++++++---- spec/models/status_spec.rb | 21 +++++++++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/app/models/favourite.rb b/app/models/favourite.rb index ce7a6a336..17f8c9fa6 100644 --- a/app/models/favourite.rb +++ b/app/models/favourite.rb @@ -32,11 +32,11 @@ class Favourite < ApplicationRecord private def increment_cache_counters - status.increment_count!(:favourites_count) + status&.increment_count!(:favourites_count) end def decrement_cache_counters return if association(:status).loaded? && (status.marked_for_destruction? || status.marked_for_mass_destruction?) - status.decrement_count!(:favourites_count) + status&.decrement_count!(:favourites_count) end end diff --git a/app/models/status.rb b/app/models/status.rb index 2eed33659..533d12354 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -396,6 +396,8 @@ class Status < ApplicationRecord private def update_status_stat!(attrs) + return if marked_for_destruction? || destroyed? + record = status_stat || build_status_stat record.update(attrs) end @@ -456,8 +458,8 @@ class Status < ApplicationRecord Account.where(id: account_id).update_all('statuses_count = COALESCE(statuses_count, 0) + 1') end - reblog.increment_count!(:reblogs_count) if reblog? - thread.increment_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?) + reblog&.increment_count!(:reblogs_count) if reblog? + thread&.increment_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?) end def decrement_counter_caches @@ -469,7 +471,7 @@ class Status < ApplicationRecord Account.where(id: account_id).update_all('statuses_count = GREATEST(COALESCE(statuses_count, 0) - 1, 0)') end - reblog.decrement_count!(:reblogs_count) if reblog? - thread.decrement_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?) + reblog&.decrement_count!(:reblogs_count) if reblog? + thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?) end end diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index b3e475d99..d03005107 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -182,6 +182,27 @@ RSpec.describe Status, type: :model do reblog.destroy expect(subject.reblogs_count).to eq 0 end + + it 'does not fail when original is deleted before reblog' do + reblog = Fabricate(:status, account: bob, reblog: subject) + expect(subject.reblogs_count).to eq 1 + expect { subject.destroy }.to_not raise_error + expect(Status.find_by(id: reblog.id)).to be_nil + end + end + + describe '#replies_count' do + it 'is the number of replies' do + reply = Fabricate(:status, account: bob, thread: subject) + expect(subject.replies_count).to eq 1 + end + + it 'is decremented when reply is removed' do + reply = Fabricate(:status, account: bob, thread: subject) + expect(subject.replies_count).to eq 1 + reply.destroy + expect(subject.replies_count).to eq 0 + end end describe '#favourites_count' do From edb1de780060a0d62bc57153d743002c0a8bde67 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 18 Aug 2018 03:44:50 +0200 Subject: [PATCH 4/7] Make CopyStatusStats migration use batches of 1000 to avoid locks (#8256) --- db/migrate/20180812173710_copy_status_stats.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/db/migrate/20180812173710_copy_status_stats.rb b/db/migrate/20180812173710_copy_status_stats.rb index 64a564ca0..0c5907c30 100644 --- a/db/migrate/20180812173710_copy_status_stats.rb +++ b/db/migrate/20180812173710_copy_status_stats.rb @@ -3,13 +3,16 @@ class CopyStatusStats < ActiveRecord::Migration[5.2] def up safety_assured do - execute <<-SQL.squish - INSERT INTO status_stats (status_id, reblogs_count, favourites_count, created_at, updated_at) - SELECT id, reblogs_count, favourites_count, created_at, updated_at - FROM statuses - ON CONFLICT (status_id) DO UPDATE - SET reblogs_count = EXCLUDED.reblogs_count, favourites_count = EXCLUDED.favourites_count - SQL + Status.where.not(id: StatusStat.select('status_id')).select('id').find_in_batches do |statuses| + execute <<-SQL.squish + INSERT INTO status_stats (status_id, reblogs_count, favourites_count, created_at, updated_at) + SELECT id, reblogs_count, favourites_count, created_at, updated_at + FROM statuses + WHERE id IN (#{statuses.map(&:id).join(', ')}) + ON CONFLICT (status_id) DO UPDATE + SET reblogs_count = EXCLUDED.reblogs_count, favourites_count = EXCLUDED.favourites_count + SQL + end end end From 5059989cc743e18f05e3f5f95ad557d7f7f34afd Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 18 Aug 2018 04:05:42 +0200 Subject: [PATCH 5/7] Use CircleCI workflows for ruby dependencies (#8228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add “bundle clean” suggestion from CircleCI doc Cf. https://circleci.com/docs/2.0/caching/#bundler-ruby * Use workspaces instead of caches for ruby gems Cache are not guaranteed to be available, while the test jobs *require* their depencies to run. Workspaces are thus more suitable. One downside is that the order of workspace layer additions need to be deterministic, which is why install-ruby{2.3,2.4} now depend on install-ruby2.5. --- .circleci/config.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ac8dfc334..20688b8e9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,12 +64,17 @@ aliases: - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version - *restore_ruby_dependencies - - run: bundle install --clean --jobs 16 --path ./vendor/bundle/ --retry 3 --with pam_authentication --without development production + - run: bundle install --clean --jobs 16 --path ./vendor/bundle/ --retry 3 --with pam_authentication --without development production && bundle clean - save_cache: key: v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} paths: - ./.bundle/ - ./vendor/bundle/ + - persist_to_workspace: + root: ~/projects/ + paths: + - ./mastodon/.bundle/ + - ./mastodon/vendor/bundle/ - &test_steps steps: @@ -78,9 +83,6 @@ aliases: - *install_system_dependencies - run: sudo apt-get install -y ffmpeg - - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version - - *restore_ruby_dependencies - - run: name: Prepare Tests command: ./bin/rails parallel:create parallel:load_schema parallel:prepare @@ -116,8 +118,6 @@ jobs: steps: - *attach_workspace - *install_system_dependencies - - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version - - *restore_ruby_dependencies - run: ./bin/rails assets:precompile - persist_to_workspace: root: ~/projects/ @@ -170,8 +170,6 @@ jobs: <<: *defaults steps: - *attach_workspace - - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version - - *restore_ruby_dependencies - run: bundle exec i18n-tasks check-normalized - run: bundle exec i18n-tasks unused @@ -186,9 +184,11 @@ workflows: - install-ruby2.4: requires: - install + - install-ruby2.5 - install-ruby2.3: requires: - install + - install-ruby2.5 - build: requires: - install-ruby2.5 From 85bb32c410a975bf6154a78a82797f82713e2428 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 18 Aug 2018 12:48:27 +0200 Subject: [PATCH 6/7] Add "sign in to participate" message to public toot page (#8200) --- app/javascript/styles/mastodon/stream_entries.scss | 12 ++++++++---- app/views/stream_entries/_status.html.haml | 6 ++++++ config/locales/en.yml | 1 + 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss index 03bbd84db..5aa809f76 100644 --- a/app/javascript/styles/mastodon/stream_entries.scss +++ b/app/javascript/styles/mastodon/stream_entries.scss @@ -37,7 +37,8 @@ &:last-child { .detailed-status, - .status { + .status, + .load-more { border-bottom: 0; border-radius: 0 0 4px 4px; } @@ -45,13 +46,15 @@ &:first-child { .detailed-status, - .status { + .status, + .load-more { border-radius: 4px 4px 0 0; } &:last-child { .detailed-status, - .status { + .status, + .load-more { border-radius: 4px; } } @@ -59,7 +62,8 @@ @media screen and (max-width: 740px) { .detailed-status, - .status { + .status, + .load-more { border-radius: 0 !important; } } diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml index 320c9bc4f..92003a48f 100644 --- a/app/views/stream_entries/_status.html.haml +++ b/app/views/stream_entries/_status.html.haml @@ -53,3 +53,9 @@ - if @next_descendant_thread .entry{ class: entry_classes } = link_to_more short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1) + +- if include_threads && !embedded_view? && !user_signed_in? + .entry{ class: entry_classes } + = link_to new_user_session_path, class: 'load-more load-gap' do + = fa_icon 'comments' + = t('statuses.sign_in_to_participate') diff --git a/config/locales/en.yml b/config/locales/en.yml index 90086103c..66ab8f10a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -746,6 +746,7 @@ en: private: Non-public toot cannot be pinned reblog: A boost cannot be pinned show_more: Show more + sign_in_to_participate: Sign in to participate in the conversation title: '%{name}: "%{quote}"' visibilities: private: Followers-only From 298ee84488758fc08a0ae9233e04c9637aa2fe02 Mon Sep 17 00:00:00 2001 From: M Somerville Date: Sat, 18 Aug 2018 11:50:32 +0100 Subject: [PATCH 7/7] Fix #8264 - Do not override ctrl/cmd+click. (#8265) This includes clicks on hashtags, mentions, display names and media in the timeline; and usernames in reply-indicator, detailed status, and the boost modal. --- app/javascript/mastodon/components/media_gallery.js | 2 +- app/javascript/mastodon/components/status.js | 2 +- app/javascript/mastodon/components/status_content.js | 4 ++-- .../mastodon/features/compose/components/reply_indicator.js | 2 +- .../mastodon/features/status/components/detailed_status.js | 2 +- app/javascript/mastodon/features/ui/components/boost_modal.js | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index 1d351279f..63bc4a59b 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -50,7 +50,7 @@ class Item extends React.PureComponent { handleClick = (e) => { const { index, onClick } = this.props; - if (e.button === 0) { + if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); onClick(index); } diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 922b609ec..e653906f1 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -65,7 +65,7 @@ export default class Status extends ImmutablePureComponent { } handleAccountClick = (e) => { - if (this.context.router && e.button === 0) { + if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { const id = e.currentTarget.getAttribute('data-id'); e.preventDefault(); this.context.router.history.push(`/accounts/${id}`); diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 9b86592f6..81013747e 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -64,7 +64,7 @@ export default class StatusContent extends React.PureComponent { } onMentionClick = (mention, e) => { - if (this.context.router && e.button === 0) { + if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); this.context.router.history.push(`/accounts/${mention.get('id')}`); } @@ -73,7 +73,7 @@ export default class StatusContent extends React.PureComponent { onHashtagClick = (hashtag, e) => { hashtag = hashtag.replace(/^#/, '').toLowerCase(); - if (this.context.router && e.button === 0) { + if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); this.context.router.history.push(`/timelines/tag/${hashtag}`); } diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js index 5b4b81eac..6f358a98b 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.js +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.js @@ -30,7 +30,7 @@ export default class ReplyIndicator extends ImmutablePureComponent { } handleAccountClick = (e) => { - if (e.button === 0) { + if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); } diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 417719004..12ffb7579 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -26,7 +26,7 @@ export default class DetailedStatus extends ImmutablePureComponent { }; handleAccountClick = (e) => { - if (e.button === 0) { + if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); } diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js index 0e9592c97..1c90d10dd 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.js +++ b/app/javascript/mastodon/features/ui/components/boost_modal.js @@ -37,7 +37,7 @@ export default class BoostModal extends ImmutablePureComponent { } handleAccountClick = (e) => { - if (e.button === 0) { + if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); this.props.onClose(); this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);