From ede655517bc78a905879f938e479238d32ee5efd Mon Sep 17 00:00:00 2001 From: Andy Piper Date: Mon, 30 Mar 2026 17:36:07 +0100 Subject: [PATCH 01/20] Update to Contributor Covenant v3.0 (#36323) Signed-off-by: Andy Piper --- CODE_OF_CONDUCT.md | 175 ++++++++++++++++----------------------------- 1 file changed, 63 insertions(+), 112 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c88a0d93855..bce574d47c1 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,132 +1,83 @@ -# Contributor Covenant Code of Conduct +# Contributor Covenant 3.0 Code of Conduct ## Our Pledge -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. +We pledge to make our community welcoming, safe, and equitable for all. -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. +We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant. -## Our Standards +## Encouraged Behaviors -Examples of behavior that contributes to a positive environment for our -community include: +While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language. -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall - community +With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including: -Examples of unacceptable behavior include: +1. Respecting the **purpose of our community**, our activities, and our ways of gathering. +2. Engaging **kindly and honestly** with others. +3. Respecting **different viewpoints** and experiences. +4. **Taking responsibility** for our actions and contributions. +5. Gracefully giving and accepting **constructive feedback**. +6. Committing to **repairing harm** when it occurs. +7. Behaving in other ways that promote and sustain the **well-being of our community**. -- The use of sexualized language or imagery, and sexual attention or advances of - any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email address, - without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting +## Restricted Behaviors -## Enforcement Responsibilities +We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct. -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. +1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop. +2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people. +3. **Stereotyping or discrimination.** Characterizing anyone’s personality or behavior on the basis of immutable identities or traits. +4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community. +5. **Violating confidentiality**. Sharing or acting on someone's personal or private information without their permission. +6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group. +7. Behaving in other ways that **threaten the well-being** of our community. -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. +### Other Restrictions + +1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions. +2. **Failing to credit sources.** Not properly crediting the sources of content you contribute. +3. **Promotional materials**. Sharing marketing or other commercial content in a way that is outside the norms of the community. +4. **Irresponsible communication.** Failing to responsibly present content which includes, links or describes any other restricted behaviors. + +## Reporting an Issue + +Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm. + +When an incident does occur, it is important to report it promptly. To report a possible violation, send an email describing the situation to hello@joinmastodon.org. + +Community Moderators take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution. + +## Addressing and Repairing Harm + +If an investigation by the Community Moderators finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped. + +1. Warning + 1. Event: A violation involving a single incident or series of incidents. + 2. Consequence: A private, written warning from the Community Moderators. + 3. Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations. +2. Temporarily Limited Activities + 1. Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation. + 2. Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members. + 3. Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over. +3. Temporary Suspension + 1. Event: A pattern of repeated violation which the Community Moderators have tried to address with warnings, or a single serious violation. + 2. Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions. + 3. Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted. +4. Permanent Ban + 1. Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Community Moderators determine there is no way to keep the community safe with this person as a member. + 2. Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior. + 3. Repair: There is no possible repair in cases of this severity. + +This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community. ## Scope -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[hello@joinmastodon.org](mailto:hello@joinmastodon.org). -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. +This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/). -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. +Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/) -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations +For answers to common questions about Contributor Covenant, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are provided at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found at [https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla’s code of conduct team](https://github.com/mozilla/inclusion). From 6736b50c1429632da3f74ab28a8a92a7f6e84f5f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:53:38 +0200 Subject: [PATCH 02/20] New Crowdin Translations (automated) (#38496) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/el.json | 4 +- app/javascript/mastodon/locales/gd.json | 233 ++++++++++++++++++++++++ app/javascript/mastodon/locales/io.json | 108 ++++++++++- app/javascript/mastodon/locales/ja.json | 1 + app/javascript/mastodon/locales/tt.json | 12 ++ app/javascript/mastodon/locales/vi.json | 18 +- config/locales/be.yml | 2 +- config/locales/doorkeeper.gd.yml | 4 + config/locales/doorkeeper.vi.yml | 4 +- config/locales/el.yml | 42 ++--- config/locales/gd.yml | 74 ++++++++ config/locales/simple_form.el.yml | 4 +- config/locales/simple_form.gd.yml | 4 + config/locales/vi.yml | 16 +- 14 files changed, 478 insertions(+), 48 deletions(-) diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 9158fcd7461..0842a8ea7da 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -480,7 +480,7 @@ "confirmation_modal.cancel": "Άκυρο", "confirmations.block.confirm": "Αποκλεισμός", "confirmations.delete.confirm": "Διαγραφή", - "confirmations.delete.message": "Σίγουρα θες να διαγράψεις αυτή την ανάρτηση;", + "confirmations.delete.message": "Σίγουρα θες να διαγράψεις αυτήν την ανάρτηση;", "confirmations.delete.title": "Διαγραφή ανάρτησης;", "confirmations.delete_collection.confirm": "Διαγραφή", "confirmations.delete_collection.message": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", @@ -1138,7 +1138,7 @@ "search.search_or_paste": "Αναζήτηση ή εισαγωγή URL", "search_popout.full_text_search_disabled_message": "Μη διαθέσιμο στο {domain}.", "search_popout.full_text_search_logged_out_message": "Διαθέσιμο μόνο όταν συνδεθείς.", - "search_popout.language_code": "Κωδικός γλώσσας ISO", + "search_popout.language_code": "κωδικός ISO γλώσσας", "search_popout.options": "Επιλογές αναζήτησης", "search_popout.quick_actions": "Γρήγορες ενέργειες", "search_popout.recent": "Πρόσφατες αναζητήσεις", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 0ca7f12e3ed..9fc37b55e06 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -14,9 +14,16 @@ "about.powered_by": "Lìonra sòisealta sgaoilte le cumhachd {mastodon}", "about.rules": "Riaghailtean an fhrithealaiche", "account.account_note_header": "Nòta pearsanta", + "account.activity": "Gnìomhachd", + "account.add_note": "Cuir nòta pearsanta ris", "account.add_or_remove_from_list": "Cuir ris no thoir air falbh o liostaichean", + "account.badges.admin": "Rianaire", + "account.badges.blocked": "’Ga bhacadh", "account.badges.bot": "Fèin-obrachail", + "account.badges.domain_blocked": "Àrainn bhacte", "account.badges.group": "Buidheann", + "account.badges.muted": "’Ga mhùchadh", + "account.badges.muted_until": "’Ga mhùchadh gu ruige {until}", "account.block": "Bac @{name}", "account.block_domain": "Bac an àrainn {domain}", "account.block_short": "Bac", @@ -27,6 +34,7 @@ "account.direct": "Thoir iomradh air @{name} gu prìobhaideach", "account.disable_notifications": "Na cuir brath thugam tuilleadh nuair a chuireas @{name} post ris", "account.domain_blocking": "Àrainn ’ga bacadh", + "account.edit_note": "Deasaich an nòta pearsanta", "account.edit_profile": "Deasaich a’ phròifil", "account.edit_profile_short": "Deasaich", "account.enable_notifications": "Cuir brath thugam nuair a chuireas @{name} post ris", @@ -36,9 +44,17 @@ "account.familiar_followers_two": "’Ga leantainn le {name1} ’s {name2}", "account.featured": "’Ga bhrosnachadh", "account.featured.accounts": "Pròifilean", + "account.featured.collections": "Cruinneachaidhean", "account.featured.hashtags": "Tagaichean hais", "account.featured_tags.last_status_at": "Am post mu dheireadh {date}", "account.featured_tags.last_status_never": "Gun phost", + "account.field_overflow": "Seall an t-susbaint shlàn", + "account.filters.all": "A’ ghnìomhachd air fad", + "account.filters.boosts_toggle": "Seall na brosnachaidhean", + "account.filters.posts_boosts": "Postaichean ’s brosnachaidhean", + "account.filters.posts_only": "Postaichean", + "account.filters.posts_replies": "Postaichean ’s freagairtean", + "account.filters.replies_toggle": "Seall na freagairtean", "account.follow": "Lean", "account.follow_back": "Lean air ais", "account.follow_back_short": "Lean air ais", @@ -63,6 +79,24 @@ "account.locked_info": "Tha prìobhaideachd ghlaiste aig a’ chunntais seo. Nì an sealbhadair lèirmheas a làimh air cò dh’fhaodas a leantainn.", "account.media": "Meadhanan", "account.mention": "Thoir iomradh air @{name}", + "account.menu.add_to_list": "Cuir ri liosta…", + "account.menu.block": "Bac an cunntas", + "account.menu.block_domain": "Bac {domain}", + "account.menu.copied": "Chaidh lethbhreac de cheangal a’ chunntais a chur air an stòr-bhòrd", + "account.menu.copy": "Dèan lethbhreac dhen cheangal", + "account.menu.direct": "Thoir iomradh air gu prìobhaideach", + "account.menu.hide_reblogs": "Falaich na brosnachaidhean air an loidhne-ama", + "account.menu.mention": "Thoir iomradh", + "account.menu.mute": "Mùch an cunntas", + "account.menu.note.description": "Chan fhaic ach thu fhèin seo", + "account.menu.open_original_page": "Seall air {domain}", + "account.menu.remove_follower": "Thoir an neach-leantainn air falbh", + "account.menu.report": "Dèan gearan mun chunntas", + "account.menu.share": "Co-roinn…", + "account.menu.show_reblogs": "Seall na brosnachaidhean air an loidhne-ama", + "account.menu.unblock": "Dì-bhac an cunntas", + "account.menu.unblock_domain": "Dì-bhac {domain}", + "account.menu.unmute": "Dì-mhùch an cunntas", "account.moved_to": "Dh’innis {name} gu bheil an cunntas ùr aca a-nis air:", "account.mute": "Mùch @{name}", "account.mute_notifications_short": "Mùch na brathan", @@ -70,7 +104,22 @@ "account.muted": "’Ga mhùchadh", "account.muting": "’Ga mhùchadh", "account.mutual": "A’ leantainn càch a chèile", + "account.name.help.domain": "Is {domain} am frithealaiche a tha ag òstadh pròifil ’s postaichean a’ chleachdaiche.", + "account.name.help.domain_self": "Is {domain} am frithealaiche agad-sa a tha ag òstadh pròifil ’s postaichean agad-sa.", + "account.name.help.footer": "Air an aon dòigh ’s a chuireas tu puist-d gu daoine le cliantan puist-d eadar-dhealaichte, ’s urrainn dhut conaltradh le daoine air frithealaichean Mastodon eile – agus le duine sam bith air aplacaidean sòisealta eile a chleachdas na h-aon riaghailtean ’s a chleachdas Mastodon (sin pròtacal ActivityPub).", + "account.name.help.header": "Tha aithnichear coltach ri seòladh puist-d", + "account.name.help.username": "Is {username} ainm-cleachdaiche a’ chunntais seo air an fhrithealaiche aca-san. Dh’fhaoidte gu bheil an t-aon ainm-cleachdaiche le cuideigin air frithealaiche eile.", + "account.name.help.username_self": "Is {username} d’ ainm-cleachdaiche air an fhrithealaiche seo. Dh’fhaoidte gu bheil an t-aon ainm-cleachdaiche le cuideigin air frithealaiche eile.", + "account.name_info": "Dè ’s ciall dha seo?", "account.no_bio": "Cha deach tuairisgeul a sholar.", + "account.node_modal.callout": "Chan fhaic ach thu fhèin na nòtaichean pearsanta.", + "account.node_modal.edit_title": "Deasaich an nòta pearsanta", + "account.node_modal.error_unknown": "Cha b’ urrainn dhuinn an nòta a shàbhaladh", + "account.node_modal.field_label": "Nòta pearsanta", + "account.node_modal.save": "Sàbhail", + "account.node_modal.title": "Cuir nòta pearsanta ris", + "account.note.edit_button": "Deasaich", + "account.note.title": "Nòta pearsanta (chan fhaic ach thu fhèin e)", "account.open_original_page": "Fosgail an duilleag thùsail", "account.posts": "Postaichean", "account.posts_with_replies": "Postaichean ’s freagairtean", @@ -81,6 +130,8 @@ "account.share": "Co-roinn a’ phròifil aig @{name}", "account.show_reblogs": "Seall na brosnachaidhean o @{name}", "account.statuses_counter": "{count, plural, one {{counter} phost} two {{counter} phost} few {{counter} postaichean} other {{counter} post}}", + "account.timeline.pinned": "Prìnichte", + "account.timeline.pinned.view_all": "Seall na postaichean prìnichte uile", "account.unblock": "Dì-bhac @{name}", "account.unblock_domain": "Dì-bhac an àrainn {domain}", "account.unblock_domain_short": "Dì-bhac", @@ -90,6 +141,95 @@ "account.unmute": "Dì-mhùch @{name}", "account.unmute_notifications_short": "Dì-mhùch na brathan", "account.unmute_short": "Dì-mhùch", + "account_edit.bio.add_label": "Cuir ris roinn mu mo dhèidhinn", + "account_edit.bio.edit_label": "Deasaich an roinn mu mo dhèidhinn", + "account_edit.bio.placeholder": "Cuir thu fhèin an aithne càich gu goirid.", + "account_edit.bio.title": "Mu mo dhèidhinn", + "account_edit.bio_modal.add_title": "Cuir ris roinn mu mo dhèidhinn", + "account_edit.bio_modal.edit_title": "Deasaich an roinn mu mo dhèidhinn", + "account_edit.column_button": "Deiseil", + "account_edit.column_title": "Deasaich a’ phròifil", + "account_edit.custom_fields.add_label": "Cuir raon ris", + "account_edit.custom_fields.edit_label": "Deasaich an raon", + "account_edit.custom_fields.placeholder": "Cuir ris do riochdairean, ceanglaichean dhan taobh a-muigh no rud sam bith eile a bu mhiann leat co-roinneadh.", + "account_edit.custom_fields.reorder_button": "Atharraich òrdugh nan raointean", + "account_edit.custom_fields.tip_title": "Gliocas: Cuir ceangalaichean dearbhte ris", + "account_edit.custom_fields.title": "Raointean gnàthaichte", + "account_edit.custom_fields.verified_hint": "Ciamar a chuireas mi ceangal dearbhte ris?", + "account_edit.display_name.add_label": "Cuir ris ainm-taisbeanaidh", + "account_edit.display_name.edit_label": "Deasaich an t-ainm-taisbeanaidh", + "account_edit.display_name.placeholder": "’S e mar a nochdas d’ ainm air a’ phròifil agad agus air loidhnichean-ama a tha san ainm-taisbeanaidh agad.", + "account_edit.display_name.title": "Ainm-taisbeanaidh", + "account_edit.featured_hashtags.edit_label": "Cuir tagaichean hais ris", + "account_edit.featured_hashtags.title": "Tagaichean hais brosnaichte", + "account_edit.field_actions.delete": "Sguab às an raon", + "account_edit.field_actions.edit": "Deasaich an raon", + "account_edit.field_delete_modal.confirm": "A bheil thu cinnteach gu bheil thu airson an raon gnàthaichte seo a sguabadh às? Cha ghabh seo a neo-dhèanamh.", + "account_edit.field_delete_modal.delete_button": "Sguab às", + "account_edit.field_delete_modal.title": "A bheil thu airson an raon gnàthaichte a sguabadh às?", + "account_edit.field_edit_modal.add_title": "Cuir raon gnàthaichte ris", + "account_edit.field_edit_modal.discard_confirm": "Tilg air falbh", + "account_edit.field_edit_modal.discard_message": "Tha atharraichean gun sàbhaladh agad. A bheil thu cinnteach gu bheil airson an tilgeil air falbh?", + "account_edit.field_edit_modal.edit_title": "Deasaich an raoin gnàthaichte", + "account_edit.field_edit_modal.name_hint": "Can “Làrach-lìn phearsanta”", + "account_edit.field_edit_modal.name_label": "Leubail", + "account_edit.field_edit_modal.url_warning": "Airson ceangal a chur ris, gabh a-staigh {protocol} aig a thoiseach.", + "account_edit.field_edit_modal.value_hint": "Can “https://example.me”", + "account_edit.field_edit_modal.value_label": "Luach", + "account_edit.field_reorder_modal.drag_cancel": "Chaidh sgur dhen t-slaodadh. Chaidh an raon “{item}” a leigeil às.", + "account_edit.field_reorder_modal.drag_end": "Chaidh an raon “{item}” a leigeil às.", + "account_edit.field_reorder_modal.drag_move": "Chaidh an raon “{item}” a ghluasad.", + "account_edit.field_reorder_modal.drag_over": "Chaidh an raon “{item}” a ghluasad thar “{over}”.", + "account_edit.field_reorder_modal.drag_start": "Chaidh an raon “{item}” a thogail.", + "account_edit.field_reorder_modal.handle_label": "Slaod an raon “{item}”", + "account_edit.field_reorder_modal.title": "Cuir òrdugh ùr air na raointean", + "account_edit.image_alt_modal.add_title": "Cuir roghainn teacsa ris", + "account_edit.image_alt_modal.details_title": "Gliocasan: Roghainn teacsa air dealbhan pròifile", + "account_edit.image_alt_modal.edit_title": "Deasaich an roghainn teacsa", + "account_edit.image_alt_modal.text_label": "Roghainn teacsa", + "account_edit.image_delete_modal.confirm": "A bheil thu cinnteach gu bheil thu airson an dealbh seo a sguabadh às? Cha ghabh seo a neo-dhèanamh.", + "account_edit.image_delete_modal.delete_button": "Sguab às", + "account_edit.image_delete_modal.title": "A bheil thu airson an dealbh a sguabadh às?", + "account_edit.image_edit.add_button": "Cuir dealbh ris", + "account_edit.image_edit.alt_add_button": "Cuir roghainn teacsa ris", + "account_edit.image_edit.alt_edit_button": "Deasaich an roghainn teacsa", + "account_edit.image_edit.remove_button": "Thoir air falbh an dealbh", + "account_edit.image_edit.replace_button": "Cuir dealbh ùr ’na àite", + "account_edit.item_list.delete": "Sguab às {name}", + "account_edit.item_list.edit": "Deasaich {name}", + "account_edit.name_modal.add_title": "Cuir ris ainm-taisbeanaidh", + "account_edit.name_modal.edit_title": "Deasaich an t-ainm-taisbeanaidh", + "account_edit.profile_tab.button_label": "Gnàthaich", + "account_edit.profile_tab.show_featured.title": "Seall an taba “’Ga bhrosnachadh”", + "account_edit.profile_tab.show_media.title": "Seall an taba “Meadhanan”", + "account_edit.profile_tab.show_media_replies.title": "Gabh a-staigh freagairtean air an taba “Meadhanan”", + "account_edit.profile_tab.subtitle": "Gnàthaich na tabaichean air a’ phròifil agad is na sheallas iad.", + "account_edit.profile_tab.title": "Roghainnean tabaichean na pròifile", + "account_edit.save": "Sàbhail", + "account_edit.upload_modal.back": "Air ais", + "account_edit.upload_modal.done": "Deiseil", + "account_edit.upload_modal.next": "Air adhart", + "account_edit.upload_modal.step_crop.zoom": "Sùm", + "account_edit.upload_modal.step_upload.button": "Rùraich na faidhlichean", + "account_edit.upload_modal.step_upload.dragging": "Leig às airson luchdadh suas", + "account_edit.upload_modal.step_upload.header": "Tagh dealbh", + "account_edit.upload_modal.step_upload.hint": "Fòrmat WEBP, PNG, GIF no JPG, suas ri {limit}MB.{br}Thèid an dealbh a sgèileadh gu {width}x{height}px.", + "account_edit.upload_modal.title_add.avatar": "Cuir dealbh ris a’ phròifil", + "account_edit.upload_modal.title_add.header": "Cuir dealbh còmhdachaidh ris", + "account_edit.upload_modal.title_replace.avatar": "Cuir dealbh ùr an àite dealbh na pròifil", + "account_edit.upload_modal.title_replace.header": "Cuir dealbh ùr an àite an deilbh chòmhdachaidh", + "account_edit.verified_modal.invisible_link.details": "Cuir an ceangal ris a’ bhann-chinn agad. ’S e rel=\"me\" a tha sa phàirt chudromach a bhacas riochd cuideigin eile air làraichean-lìn le susbaint air a gintinn o chleachdaiche. ’S urrainn dhut fiù taga link a chleachdadh ann am bann-cinn na duilleige seach {tag} ach feumaidh sinn an HTML ruigsinn gun a bhith a’ ruith JavaScript.", + "account_edit.verified_modal.invisible_link.summary": "Ciamar a dh’fhalaicheas mi an ceangal?", + "account_edit.verified_modal.step1.header": "Dèan lethbhreac dhen chòd HTML gu h-ìosal is cuir e ri bann-cinn na làraich-lìn agad", + "account_edit.verified_modal.step2.details": "Ma chuir thu an làrach-lìn agad mar raon ghnàthaichte ris cheana, feumaidh tu a sguabadh às ’s a chur ris a-rithist airson an dearbhadh a chur gu dol.", + "account_edit.verified_modal.step2.header": "Cuir an làrach-lìn agad ris na raon gnàthaichte", + "account_edit.verified_modal.title": "Mar a chuireas tu ceangal dearbhte ris", + "account_edit_tags.add_tag": "Cuir #{tagName} ris", + "account_edit_tags.column_title": "Deasaich na tagaichean", + "account_edit_tags.search_placeholder": "Cuir a-steach taga hais…", + "account_edit_tags.suggestions": "Molaidhean:", + "account_edit_tags.tag_status_count": "{count, plural, one {# phost} two {# phost} few {# postaichean} other {# post}}", + "account_list.total": "{total, plural, one {# chunntas} two {# chunntas} few {# cunntasan} other {# cunntas}}", "account_note.placeholder": "Briog airson nòta a chur ris", "admin.dashboard.daily_retention": "Reat glèidheadh nan cleachdaichean às dèidh an clàradh a-rèir latha", "admin.dashboard.monthly_retention": "Reat glèidheadh nan cleachdaichean às dèidh an clàradh a-rèir mìos", @@ -184,16 +324,74 @@ "bundle_modal_error.close": "Dùin", "bundle_modal_error.message": "Chaidh rudeigin ceàrr le luchdadh na sgrìn seo.", "bundle_modal_error.retry": "Feuch ris a-rithist", + "callout.dismiss": "Leig seachad", "carousel.current": "Sleamhnag {current, number} / {max, number}", "carousel.slide": "Sleamhnag {current, number} à {max, number}", + "character_counter.recommended": "{currentLength}/{maxLength} dhe na caractaran a mholamaid", + "character_counter.required": "{currentLength}/{maxLength} caractar(an)", "closed_registrations.other_server_instructions": "Air sgàth ’s gu bheil Mastodon sgaoilte, ’s urrainn dhut cunntas a chruthachadh air frithealaiche eile agus conaltradh ris an fhrithealaiche seo co-dhiù.", "closed_registrations_modal.description": "Cha ghabh cunntas a chruthachadh air {domain} aig an àm seo ach thoir an aire nach fheum thu cunntas air {domain} gu sònraichte airson Mastodon a chleachdadh.", "closed_registrations_modal.find_another_server": "Lorg frithealaiche eile", "closed_registrations_modal.preamble": "Tha Mastodon sgaoilte is mar sin dheth ge b’ e càit an cruthaich thu an cunntas agad, ’s urrainn dhut duine sam bith a leantainn air an fhrithealaiche seo is conaltradh leotha. ’S urrainn dhut fiù ’s frithealaiche agad fhèin òstadh!", "closed_registrations_modal.title": "Clàradh le Mastodon", + "collection.share_modal.share_link_label": "Ceangal co-roinnidh", + "collection.share_modal.share_via_post": "Postaich air Mastodon", + "collection.share_modal.share_via_system": "Co-roinn gu…", + "collection.share_modal.title": "Co-roinn an cruinneachadh", + "collection.share_modal.title_new": "Co-roinn an cruinneachadh ùr agad!", + "collection.share_template_other": "Thoir sùil air an deagh-chruinneachadh seo: {link}", + "collection.share_template_own": "Thoir sùil air a’ chruinneachadh ùr agam: {link}", + "collections.account_count": "{count, plural, one {# chunntas} two {# chunntas} few {# cunntasan} other {# cunntas}}", + "collections.accounts.empty_description": "Cuir ris suas ri {count} cunntas(an) a tha thu a’ leantainn", + "collections.accounts.empty_title": "Tha an an cruinneachadh seo falamh", + "collections.by_account": "le {account_handle}", + "collections.collection_description": "Tuairisgeul", + "collections.collection_language": "Cànan", + "collections.collection_language_none": "Chan eil gin", + "collections.collection_name": "Ainm", + "collections.collection_topic": "Cuspair", + "collections.confirm_account_removal": "A bheil thu cinnteach gu bheil thu airson an cunntas seo a thoirt air falbh on chruinneachadh seo?", + "collections.content_warning": "Rabhadh susbainte", + "collections.continue": "Lean air adhart", + "collections.create.basic_details_title": "Bun-fhiosrachadh", + "collections.create.steps": "Ceum {step}/{total}", + "collections.create_collection": "Cruthaich cruinneachadh", + "collections.delete_collection": "Sguab an cruinneachadh às", + "collections.description_length_hint": "Crìoch de 100 caractar", + "collections.detail.accept_inclusion": "Taghta", + "collections.detail.accounts_heading": "Cunntasan", + "collections.detail.author_added_you": "Chuir {author} ris a’ chruinneachadh seo thu", + "collections.detail.loading": "A’ luchdadh a’ chruinneachaidh…", + "collections.detail.other_accounts_in_collection": "Daoine eile sa chruinneachadh seo:", + "collections.detail.revoke_inclusion": "Thoir air falbh mi", + "collections.detail.share": "Co-roinn an cruinneachadh seo", + "collections.edit_details": "Deasaich am fiosrachadh", + "collections.error_loading_collections": "Thachair mearachd nuair a dh’fheuch sinn ris a’ chruinneachaidhean agad a luchdadh.", + "collections.hints.accounts_counter": "{count} / {max} cunntas(an)", + "collections.last_updated_at": "An tùrachadh mu dheireadh: {date}", + "collections.manage_accounts": "Stiùirich na cunntasan", + "collections.mark_as_sensitive": "Cuir comharra gu bheil e frionasach", + "collections.name_length_hint": "Crìoch de 40 caractar", + "collections.new_collection": "Cruinneachadh ùr", + "collections.no_collections_yet": "Chan eil cruinneachadh agad fhathast.", + "collections.old_last_post_note": "Tha am post mu dheireadh còrr is seachdain air ais", + "collections.remove_account": "Thoir air falbh an cunntas seo", + "collections.report_collection": "Dèan gearan mun chruinneachadh seo", + "collections.revoke_collection_inclusion": "Thoir mi fhìn air falbh on chruinneachadh seo", + "collections.revoke_inclusion.confirmation": "Chaidh do thoirt air falbh o “{collection}”", + "collections.revoke_inclusion.error": "Thachair mearachd. Feuch ris a-rithist an ceann greis.", + "collections.search_accounts_label": "Lorg cunntasan gus an cur ris…", + "collections.sensitive": "Frionasach", + "collections.view_collection": "Seall an cruinneachadh ", + "collections.view_other_collections_by_user": "Seall cruinneachaidhean eile aig a’ chleachdaiche seo", + "collections.visibility_public": "Poblach", + "collections.visibility_title": "Faicsinneachd", + "collections.visibility_unlisted": "Falaichte o liostaichean", + "collections.visibility_unlisted_hint": "Chì duine sam bith aig a bheil ceangal e. Thèid fhalach o thoraidhean luirg ’s na molaidhean.", "column.about": "Mu dhèidhinn", "column.blocks": "Cleachdaichean bacte", "column.bookmarks": "Comharran-lìn", + "column.collections": "Na cruinneachaidhean agam", "column.community": "Loidhne-ama ionadail", "column.create_list": "Cruthaich liosta", "column.direct": "Iomraidhean prìobhaideach", @@ -220,6 +418,11 @@ "column_header.show_settings": "Seall na roghainnean", "column_header.unpin": "Dì-phrìnich", "column_search.cancel": "Sguir dheth", + "combobox.close_results": "Dùin na toraidhean", + "combobox.loading": "’Ga luchdadh", + "combobox.no_results_found": "Cha deach toradh a lorg", + "combobox.open_results": "Fosgail na toraidhean", + "combobox.results_available": "Tha {count, plural, one {# mholadh} two {# mholadh} few {# molaidhean } other {# moladh}} ri fhaighinn. Cleachd an t-saighead suas no sìos airson gluasad mun cuairt. Brùth air Enter airson taghadh.", "community.column_settings.local_only": "Feadhainn ionadail a-mhàin", "community.column_settings.media_only": "Meadhanan a-mhàin", "community.column_settings.remote_only": "Feadhainn chèin a-mhàin", @@ -253,6 +456,9 @@ "confirmations.delete.confirm": "Sguab às", "confirmations.delete.message": "A bheil thu cinnteach gu bheil thu airson am post seo a sguabadh às?", "confirmations.delete.title": "A bheil thu airson am post a sguabadh às?", + "confirmations.delete_collection.confirm": "Sguab às", + "confirmations.delete_collection.message": "Cha ghabh seo a neo-dhèanamh.", + "confirmations.delete_collection.title": "A bheil thu airson “{name}” a sguabadh às?", "confirmations.delete_list.confirm": "Sguab às", "confirmations.delete_list.message": "A bheil thu cinnteach gu bheil thu airson an liosta seo a sguabadh às gu buan?", "confirmations.delete_list.title": "A bheil thu airson an liosta a sguabadh às?", @@ -265,6 +471,9 @@ "confirmations.discard_draft.post.title": "A bheil thu airson dreachd a’ phuist agad a thilgeil air falbh?", "confirmations.discard_edit_media.confirm": "Tilg air falbh", "confirmations.discard_edit_media.message": "Tha atharraichean gun sàbhaladh agad ann an tuairisgeul no ro-shealladh a’ mheadhain, a bheil thu airson an tilgeil air falbh co-dhiù?", + "confirmations.follow_to_collection.confirm": "Lean ’s cuir ri cruinneachadh", + "confirmations.follow_to_collection.message": "Feumaidh tu {name} a leantainn mus cuir thu ri cruinneachadh iad.", + "confirmations.follow_to_collection.title": "A bheil thu airson an cunntas a leantainn?", "confirmations.follow_to_list.confirm": "Lean ’s cuir ris an liosta", "confirmations.follow_to_list.message": "Feumaidh tu {name} a leantainn ron chur ri liosta.", "confirmations.follow_to_list.title": "A bheil thu airson an cleachdaiche a leantainn?", @@ -291,6 +500,8 @@ "confirmations.remove_from_followers.confirm": "Thoir an neach-leantainn air falbh", "confirmations.remove_from_followers.message": "Cha lean {name} thu tuilleadh. A bheil thu cinnteach gu bheil thu airson leantainn air adhart?", "confirmations.remove_from_followers.title": "A bheil thu airson an neach-leantainn a thoirt air falbh?", + "confirmations.revoke_collection_inclusion.confirm": "Thoir air falbh mi", + "confirmations.revoke_collection_inclusion.title": "A bheil tu airson do thoirt air falbh on chruinneachadh seo?", "confirmations.revoke_quote.confirm": "Thoir am post air falbh", "confirmations.revoke_quote.message": "Cha ghabh seo a neo-dhèanamh.", "confirmations.revoke_quote.title": "A bheil thu airson am post a thoirt air falbh?", @@ -308,6 +519,7 @@ "conversation.open": "Seall an còmhradh", "conversation.with": "Còmhla ri {names}", "copy_icon_button.copied": "Chaidh lethbhreac dheth a chur air an stòr-bhòrd", + "copy_icon_button.copy_this_text": "Cuir lethbhreac dhen cheangal air an stòr-bhòrd", "copypaste.copied": "Chaidh lethbhreac dheth a dhèanamh", "copypaste.copy_to_clipboard": "Cuir lethbhreac dheth air an stòr-bhòrd", "directory.federated": "On cho-shaoghal aithnichte", @@ -384,6 +596,7 @@ "empty_column.notification_requests": "Glan! Chan eil dad an-seo. Nuair a gheibh thu brathan ùra, nochdaidh iad an-seo a-rèir nan roghainnean agad.", "empty_column.notifications": "Cha d’ fhuair thu brath sam bith fhathast. Nuair a nì càch conaltradh leat, chì thu an-seo e.", "empty_column.public": "Chan eil dad an-seo! Sgrìobh rudeigin gu poblach no lean càch o fhrithealaichean eile a làimh airson seo a lìonadh", + "empty_state.no_results": "Gun toradh", "error.no_hashtag_feed_access": "Cruthaich cunntas no clàraich a-steach airson an taga hais seo a shealltainn is leantainn.", "error.unexpected_crash.explanation": "Air sàilleibh buga sa chòd againn no duilgheadas co-chòrdalachd leis a’ bhrabhsair, chan urrainn dhuinn an duilleag seo a shealltainn mar bu chòir.", "error.unexpected_crash.explanation_addons": "Cha b’ urrainn dhuinn an duilleag seo a shealltainn mar bu chòir. Tha sinn an dùil gu do dh’adhbharaich tuilleadan a’ bhrabhsair no inneal eadar-theangachaidh fèin-obrachail a’ mhearachd.", @@ -399,6 +612,10 @@ "featured_carousel.current": "Post {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Post prìnichte} two {Postaichean prìnichte} few {Postaichean prìnichte} other {Postaichean prìnichte}}", "featured_carousel.slide": "Post {current, number} à {max, number}", + "featured_tags.suggestions": "Sgrìobh thu mu {items} o chionn goirid. A bheil thu airson an cur ris ’nan tagaichean hais brosnaichte?", + "featured_tags.suggestions.add": "Cuir ris", + "featured_tags.suggestions.added": "Stiùirich na tagaichean hais brosnaichte agad uair sam bith aig Deasaich a’ phròifil > Tagaichean hais brosnaichte.", + "featured_tags.suggestions.dismiss": "Na cuir ris", "filter_modal.added.context_mismatch_explanation": "Chan eil an roinn-seòrsa criathraidh iom seo chaidh dhan cho-theacs san do dh’inntrig thu am post seo. Ma tha thu airson am post a chriathradh sa cho-theacs seo cuideachd, feumaidh tu a’ chriathrag a dheasachadh.", "filter_modal.added.context_mismatch_title": "Co-theacsa neo-iomchaidh!", "filter_modal.added.expired_explanation": "Dh’fhalbh an ùine air an roinn-seòrsa criathraidh seo agus feumaidh tu an ceann-là crìochnachaidh atharrachadh mus cuir thu an sàs i.", @@ -440,6 +657,8 @@ "follow_suggestions.view_all": "Seall na h-uile", "follow_suggestions.who_to_follow": "Molaidhean leantainn", "followed_tags": "Tagaichean hais ’gan leantainn", + "followers.title": "A’ leantainn {name}", + "following.title": "’Ga leantainn le {name}", "footer.about": "Mu dhèidhinn", "footer.about_mastodon": "Mu Mhastodon", "footer.about_server": "Mu {domain}", @@ -451,6 +670,8 @@ "footer.source_code": "Seall am bun-tùs", "footer.status": "Staid", "footer.terms_of_service": "Teirmichean na seirbheise", + "form_error.blank": "Chan fhaod an raon a bhith bàn.", + "form_field.optional": "(roghainneil)", "generic.saved": "Chaidh a shàbhaladh", "getting_started.heading": "Toiseach", "hashtag.admin_moderation": "Fosgail eadar-aghaidh na maorsainneachd dha #{name}", @@ -520,6 +741,7 @@ "keyboard_shortcuts.direct": "Fosgail colbh nan iomraidhean prìobhaideach", "keyboard_shortcuts.down": "Gluais sìos air an liosta", "keyboard_shortcuts.enter": "Fosgail post", + "keyboard_shortcuts.explore": "Fosgail na treandaichean", "keyboard_shortcuts.favourite": "Cuir am post ris na h-annsachdan", "keyboard_shortcuts.favourites": "Fosgail liosta nan annsachdan", "keyboard_shortcuts.federated": "Fosgail an loidhne-ama cho-naisgte", @@ -606,6 +828,7 @@ "navigation_bar.automated_deletion": "Sguabadh às phostaichean", "navigation_bar.blocks": "Cleachdaichean bacte", "navigation_bar.bookmarks": "Comharran-lìn", + "navigation_bar.collections": "Cruinneachaidhean", "navigation_bar.direct": "Iomraidhean prìobhaideach", "navigation_bar.domain_blocks": "Àrainnean bacte", "navigation_bar.favourites": "Annsachdan", @@ -753,12 +976,14 @@ "notifications_permission_banner.title": "Na caill dad gu bràth tuilleadh", "onboarding.follows.back": "Air ais", "onboarding.follows.empty": "Gu mì-fhortanach, chan urrainn dhuinn toradh a shealltainn an-dràsta. Feuch gleus an luirg no duilleag an rùrachaidh airson daoine ri leantainn a lorg no feuch ris a-rithist an ceann tamaill.", + "onboarding.follows.next": "Air adhart: Suidhich a’ phròifil agad", "onboarding.follows.search": "Lorg", "onboarding.follows.title": "Lean daoine airson tòiseachadh", "onboarding.profile.discoverable": "Bu mhath leam gun gabh a’ phròifil agam a rùrachadh", "onboarding.profile.discoverable_hint": "Ma chuir thu romhad gun gabh a’ phròifil agad a rùrachadh air Mastodon, faodaidh na postaichean agad nochdadh ann an toraidhean luirg agus treandaichean agus dh’fhaoidte gun dèid a’ phròifil agad a mholadh dhan fheadhainn aig a bheil ùidhean coltach ri d’ ùidhean-sa.", "onboarding.profile.display_name": "Ainm-taisbeanaidh", "onboarding.profile.display_name_hint": "D’ ainm slàn no spòrsail…", + "onboarding.profile.finish": "Crìochnaich", "onboarding.profile.note": "Cunntas-beatha", "onboarding.profile.note_hint": "’S urrainn dhut @iomradh a thoirt air càch no air #tagaicheanHais…", "onboarding.profile.title": "Suidheachadh na pròifile", @@ -784,6 +1009,7 @@ "privacy.private.short": "Luchd-leantainn", "privacy.public.long": "Duine sam bith taobh a-staigh no a-muigh Mhastodon", "privacy.public.short": "Poblach", + "privacy.quote.anyone": "{visibility}, luaidh ceadaichte", "privacy.quote.disabled": "{visibility}, luaidh à comas", "privacy.quote.limited": "{visibility}, luaidh cuingichte", "privacy.unlisted.additional": "Tha seo coltach ris an fhaicsinneachd phoblach ach cha nochd am post air loidhnichean-ama an t-saoghail phoblaich, nan tagaichean hais no an rùrachaidh no ann an toraidhean luirg Mhastodon fiù ’s ma thug thu ro-aonta airson sin seachad.", @@ -829,6 +1055,7 @@ "report.category.title_account": "phròifil", "report.category.title_status": "phost", "report.close": "Deiseil", + "report.collection_comment": "Carson a tha thu airson gearan a dhèanamh mun chruinneachadh seo?", "report.comment.title": "A bheil rud sam bith eile a bu toigh leat innse dhuinn?", "report.forward": "Sìn air adhart gu {target}", "report.forward_hint": "Chaidh an cunntas a chlàradh air frithealaiche eile. A bheil thu airson lethbhreac dhen ghearan a chur dha-san gun ainm cuideachd?", @@ -850,6 +1077,8 @@ "report.rules.title": "Dè na riaghailtean a tha ’gam briseadh?", "report.statuses.subtitle": "Tagh a h-uile gin a tha iomchaidh", "report.statuses.title": "A bheil postaichean sam bith ann a tha ’nam fianais dhan ghearan seo?", + "report.submission_error": "Cha b’ urrainn dhuinn an gearan a chur a-null", + "report.submission_error_details": "Thoir sùil air a’ cheangal ris an lìonra agad is feuch ris a-rithist an ceann greis.", "report.submit": "Cuir a-null", "report.target": "A’ gearan mu {target}", "report.thanks.take_action": "Seo na roghainnean a th’ agad airson stiùireadh na chì thu air Mastodon:", @@ -903,6 +1132,9 @@ "sign_in_banner.mastodon_is": "Is Mastodon an dòigh as fheàrr airson sùil a chumail air na tha a’ dol.", "sign_in_banner.sign_in": "Clàraich a-steach", "sign_in_banner.sso_redirect": "Clàraich a-steach no clàraich leinn", + "skip_links.hotkey": "Grad-iuchair {hotkey}", + "skip_links.skip_to_content": "Geàrr leum chun na prìomh-shusbainte", + "skip_links.skip_to_navigation": "Geàrr leum chun na prìomh-sheòladaireachd", "status.admin_account": "Fosgail eadar-aghaidh na maorsainneachd dha @{name}", "status.admin_domain": "Fosgail eadar-aghaidh na maorsainneachd dha {domain}", "status.admin_status": "Fosgail am post seo ann an eadar-aghaidh na maorsainneachd", @@ -1004,6 +1236,7 @@ "tabs_bar.notifications": "Brathan", "tabs_bar.publish": "Post ùr", "tabs_bar.search": "Lorg", + "tag.remove": "Thoir air falbh", "terms_of_service.effective_as_of": "Èifeachdach on {date}", "terms_of_service.title": "Teirmichean na seirbheise", "terms_of_service.upcoming_changes_on": "Tha atharraichean ri thighinn air {date}", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index c0bbf7f4ffa..031a45819e2 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -1,6 +1,7 @@ { "about.blocks": "Jerata servili", "about.contact": "Kontaktajo:", + "about.default_locale": "Predeterminita", "about.disclaimer": "Mastodon esas libera, publikfonta e komercmarko di Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Expliko nedisponebla", "about.domain_blocks.preamble": "Mastodon generale permisas on vidar kontenajo e interagar kun uzanti de irga altra servilo en fediverso. Existas eceptioni quo facesis che ca partikulara servilo.", @@ -13,30 +14,55 @@ "about.powered_by": "Necentraligita sociala ret quo povigesas da {mastodon}", "about.rules": "Servilreguli", "account.account_note_header": "Personala noto", + "account.activity": "Ago", + "account.add_note": "Adjuntez personala noto", "account.add_or_remove_from_list": "Adjuntar o forigar de listi", + "account.badges.admin": "Administranto", + "account.badges.blocked": "Blokusita", "account.badges.bot": "Boto", + "account.badges.domain_blocked": "Blokusita domeno", "account.badges.group": "Grupo", + "account.badges.muted": "Silencigata", + "account.badges.muted_until": "Silencigita til {until}", "account.block": "Blokusar @{name}", "account.block_domain": "Blokusar {domain}", "account.block_short": "Blokusar", "account.blocked": "Blokusita", + "account.blocking": "Blokusas", "account.cancel_follow_request": "Desendez sequodemando", "account.copy": "Kopiez ligilo al profilo", "account.direct": "Private mencionez @{name}", "account.disable_notifications": "Cesez avizar me kande @{name} postas", + "account.domain_blocking": "Blokusas domeno", + "account.edit_note": "Redaktez personala noto", "account.edit_profile": "Redaktar profilo", + "account.edit_profile_short": "Redaktez", "account.enable_notifications": "Avizez me kande @{name} postas", "account.endorse": "Traito di profilo", + "account.featured": "Estalita", + "account.featured.accounts": "Profili", + "account.featured.collections": "Kolektaji", + "account.featured.hashtags": "Gretvorti", "account.featured_tags.last_status_at": "Antea posto ye {date}", "account.featured_tags.last_status_never": "Nula posti", + "account.filters.all": "Omna ago", + "account.filters.boosts_toggle": "Montrez diskonoci", + "account.filters.posts_boosts": "Afishi e diskonoci", + "account.filters.posts_only": "Afishi", + "account.filters.posts_replies": "Afishi e respondi", + "account.filters.replies_toggle": "Montrez respondi", "account.follow": "Sequar", "account.follow_back": "Anke sequez", + "account.follow_request_cancel": "Anulez demando", + "account.follow_request_cancel_short": "Anulez", + "account.follow_request_short": "Demandez", "account.followers": "Sequanti", "account.followers.empty": "Nulu sequas ca uzanto til nun.", "account.followers_counter": "{count, plural,one {{counter} sequanto} other {{counter} sequanti}}", "account.following": "Sequata", "account.following_counter": "{count, plural,one {{counter} sequato} other {{counter} sequati}}", "account.follows.empty": "Ca uzanto ne sequa irgu til nun.", + "account.follows_you": "Sequas vu", "account.go_to_profile": "Irez al profilo", "account.hide_reblogs": "Celez repeti de @{name}", "account.in_memoriam": "Memorige.", @@ -46,12 +72,29 @@ "account.locked_info": "La privatesostaco di ca konto fixesas quale lokata. Proprietato manue kontrolas personi qui povas sequar.", "account.media": "Audvidaji", "account.mention": "Mencionar @{name}", + "account.menu.add_to_list": "Adjuntez ad listo…", + "account.menu.block": "Blokusez konto", + "account.menu.block_domain": "Blokuez {domain}", + "account.menu.direct": "Private mencionez", + "account.menu.hide_reblogs": "Celez diskonoci en tempolineo", + "account.menu.mention": "Mencionez", + "account.menu.mute": "Silencigez konto", + "account.menu.open_original_page": "Videz che {domain}", + "account.menu.remove_follower": "Efacez sequanto", + "account.menu.report": "Efacez konto", "account.menu.share": "Kunhavigez…", + "account.menu.show_reblogs": "Montrez diskonoci en tempolineo", + "account.menu.unblock": "Retroblokusez konto", + "account.menu.unblock_domain": "Retroblokusez {domain}", + "account.menu.unmute": "Retrosilencigez konto", "account.moved_to": "{name} indikis ke lua nova konto es nune:", "account.mute": "Celar @{name}", "account.mute_notifications_short": "Silencigez avizi", "account.mute_short": "Silencigez", "account.muted": "Silencigata", + "account.muting": "Silencigas", + "account.name.help.footer": "Samkam vu povas sendar retposti ad personi per dessama retpostosoftwari, vu povas interagar kun personi che altra Mastodon-servili (ActivityPub-kodexaro).", + "account.name.help.header": "Nometo esas kam retpostadreso", "account.no_bio": "Deskriptajo ne provizesis.", "account.open_original_page": "Apertez originala pagino", "account.posts": "Mesaji", @@ -64,12 +107,38 @@ "account.unblock": "Desblokusar @{name}", "account.unblock_domain": "Desblokusar {domain}", "account.unblock_short": "Desblokusar", - "account.unendorse": "Ne publikigez che profilo", + "account.unendorse": "Ne estalez che profilo", "account.unfollow": "Ne plus sequar", "account.unmute": "Ne plus celar @{name}", "account.unmute_notifications_short": "Dessilencigez avizi", "account.unmute_short": "Desilencigez", "account_edit.custom_fields.placeholder": "Adjuntez vua pronomi, externa ligili o altra irgo quan vu volas kunhavigar.", + "account_edit.display_name.add_label": "Adjuntez nometo", + "account_edit.display_name.edit_label": "Redaktez nometo", + "account_edit.display_name.placeholder": "Vua nometo esas quale vua nomo aparas che vua profilo e en tempolinei.", + "account_edit.display_name.title": "Nometo", + "account_edit.featured_hashtags.edit_label": "Adjuntez gretvorti", + "account_edit.featured_hashtags.placeholder": "Helpez altru identifikar e havez rapida aceso por vua prizita topiki.", + "account_edit.featured_hashtags.title": "Estalita gretvorti", + "account_edit.field_edit_modal.length_warning": "Rekomendita literlimito esis ecesita.", + "account_edit.field_edit_modal.link_emoji_warning": "Ni desrekomendas uzar kustuma emoji kun url.", + "account_edit.field_edit_modal.url_warning": "Por adjuntar ligilo, inkluzez {protocol} che la komenco.", + "account_edit.name_modal.add_title": "Adjuntez nometo", + "account_edit.name_modal.edit_title": "Redaktez nometo", + "account_edit.profile_tab.button_label": "Kustumizez", + "account_edit.profile_tab.hint.description": "Ca preferaji kustumizas quon uzanti vidas che {server} per oficala softwari, me li forsan ne esas sama ad uzanti che altra servili e desoficala softwari.", + "account_edit.profile_tab.hint.title": "Vidi ankore esas chanjita", + "account_edit.profile_tab.show_featured.description": "'Estalita' esas nemusta langeto ube vu povas dismontrar altra konti.", + "account_edit.profile_tab.show_featured.title": "Montrez 'Estalita'-langeto", + "account_edit.profile_tab.show_media.description": "'Audvidajo' esas desmusta langeto qua montras vua posti qui kontenas imaji o videii.", + "account_edit.profile_tab.show_media.title": "Montrez 'Audvidajo'-langeto", + "account_edit.profile_tab.show_media_replies.description": "Kande aktivigita, audvidajlangeto montras ambe vua afishi e respondi ad afishi di altra personi.", + "account_edit.profile_tab.show_media_replies.title": "Inkluzas respondi che 'Audvidajo'-langeto", + "account_edit.profile_tab.subtitle": "Kustumizas la langeti che vua profilo e quon li montras.", + "account_edit.profile_tab.title": "Profillangetpreferaji", + "account_edit.verified_modal.invisible_link.details": "Adjuntez la ligilo ad vua kapimajo. Vu anke povas uzar ligiletiketo en la kapimajo di la pagino vice {tag}, ma la HTML mustas esar acesebla sen exekutar JavaScript.", + "account_edit_tags.help_text": "Estalita gretvorti helpas uzanti deskovrar e interagar kun vua profilo.", + "account_edit_tags.max_tags_reached": "Vu atingas la maxim quanto di estalita gretvorti.", "account_note.placeholder": "Klikez por adjuntar noto", "admin.dashboard.daily_retention": "Dia uzantoretenseso pos registro", "admin.dashboard.monthly_retention": "Monata uzantoreteneso pos registro", @@ -96,6 +165,12 @@ "annual_report.shared_page.donate": "Donacez", "annual_report.shared_page.footer": "Igita kun {heart} da la grupo di Mastodon", "annual_report.shared_page.footer_server_info": "{username} uzas {domain}, un de multa komunitati quin povizita da Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} dauras serchar afishi por diskonoci e pluigas altra kreanti.", + "annual_report.summary.archetype.booster.desc_self": "Vu dauras serchar afishi por diskonoci e pluigas altra kreanti.", + "annual_report.summary.archetype.booster.name": "La arkisto", + "annual_report.summary.archetype.lurker.desc_public": "Ni povas ke {name} esas ulaloke e juas Mastodon.", + "annual_report.summary.archetype.lurker.desc_self": "Ni savas ke vu esas ulaloke e juas Mastodon.", + "annual_report.summary.highlighted_post.boost_count": "Ca afisho esis diskonocita {count, plural,one {unfoye} other {# foye}}.", "annual_report.summary.most_used_app.most_used_app": "maxim uzita aplikajo", "annual_report.summary.most_used_hashtag.most_used_hashtag": "maxim uzita gretvorto", "annual_report.summary.new_posts.new_posts": "nova afishi", @@ -141,8 +216,11 @@ "collection.share_modal.title_new": "Kunhavigez vua nova kolektajo!", "collection.share_template_other": "Videz ca splendida kolektajo: {link}", "collection.share_template_own": "Videz mia nova kolektajo: {link}", + "collections.content_warning": "Kontenajaverto", + "collections.create.accounts_title": "Quan vu estalos en ca kolektajo?", "collections.create_a_collection_hint": "Kreez kolektajo por rekomendar o kunhavigez vua favoriza konti kun altri.", "collections.detail.share": "Kunhavigez ca kolektajo", + "collections.mark_as_sensitive_hint": "Celas deskripto di la kolektajo e konti dop kontenajaverto.", "column.about": "Pri co", "column.blocks": "Blokusita uzeri", "column.bookmarks": "Lektosigni", @@ -292,6 +370,9 @@ "emoji_button.search_results": "Trovuri", "emoji_button.symbols": "Simboli", "emoji_button.travel": "Vizito & Plasi", + "empty_column.account_featured.me": "Vu ne estalas irga ankore. Ka vu savas?", + "empty_column.account_featured.other": "{acct} ne estalas irga ankore. Ka vu savas?", + "empty_column.account_featured_other.unknown": "Ca konto ne estalas irga ankore.", "empty_column.account_hides_collections": "Ca uzanto selektis ne publikigar ca informo", "empty_column.account_suspended": "Konto restriktesis", "empty_column.account_timeline": "No toots here!", @@ -324,6 +405,14 @@ "explore.trending_links": "Novaji", "explore.trending_statuses": "Posti", "explore.trending_tags": "Hashtagi", + "featured_carousel.current": "Afisho {current, number} / {max, number}", + "featured_carousel.header": "{count, plural, one {Pinglizita Afisho} other {Pinglizita Afishi}}", + "featured_carousel.slide": "Afisho {current, number} ek {max, number}", + "featured_tags.more_items": "+{count}", + "featured_tags.suggestions": "Recente vu afishigis pri {items}. Ka adjuntez ci kom estalita gretvorti?", + "featured_tags.suggestions.add": "Adjuntez", + "featured_tags.suggestions.added": "Aranjez vua estalita gretvorti irgatempe che Redaktez Profilo > Estalita gretvorti.", + "featured_tags.suggestions.dismiss": "Nedanki", "filter_modal.added.context_mismatch_explanation": "Ca filtrilgrupo ne uzesis ad informo di ca adirita afisho.", "filter_modal.added.context_mismatch_title": "Kontenajneparigeso!", "filter_modal.added.expired_explanation": "Ca filtrilgrupo expiris, vu bezonas chanjar expirtempo por apliko.", @@ -388,7 +477,9 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} partoprenanto} other {{counter} partoprenanti}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} posto} other {{counter} posti}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} posto} other {{counter} posti}} hodie", + "hashtag.feature": "Estalez che profilo", "hashtag.follow": "Sequar gretvorto", + "hashtag.unfeature": "Ne estalez che profilo", "hashtag.unfollow": "Dessequar gretvorto", "hashtags.and_other": "…e {count, plural, one {# plusa}other {# plusa}}", "hints.profiles.followers_may_be_missing": "Sequanti di ca profilo forsan ne esas hike.", @@ -511,8 +602,10 @@ "mute_modal.you_wont_see_mentions": "Vu ne vidos posti qua mencionas lu.", "mute_modal.you_wont_see_posts": "Lu ankore povas vidar vua posti, ma vu ne vidos lua.", "navigation_bar.about": "Pri co", + "navigation_bar.account_settings": "Pasvorto e sekureso", "navigation_bar.administration": "Administro", "navigation_bar.advanced_interface": "Apertez per retintervizajo", + "navigation_bar.automated_deletion": "Automata postoefaco", "navigation_bar.blocks": "Blokusita uzeri", "navigation_bar.bookmarks": "Lektosigni", "navigation_bar.direct": "Privata mencioni", @@ -522,12 +615,14 @@ "navigation_bar.follow_requests": "Demandi di sequado", "navigation_bar.followed_tags": "Hashtagi sequita", "navigation_bar.follows_and_followers": "Sequati e sequanti", + "navigation_bar.import_export": "Importaco e exportaco", "navigation_bar.lists": "Listi", "navigation_bar.logout": "Ekirar", "navigation_bar.moderation": "Jero", "navigation_bar.mutes": "Celita uzeri", "navigation_bar.opened_in_classic_interface": "Posti, konti e altra pagini specifika apertesas en la retovidilo klasika.", "navigation_bar.preferences": "Preferi", + "navigation_bar.privacy_and_reach": "Privateso e atingeso", "navigation_bar.search": "Serchez", "navigation_bar.search_trends": "Sercho / Tendenco", "not_signed_in_indicator.not_signed_in": "Vu mustas enirar por acesar ca moyeno.", @@ -806,7 +901,7 @@ "status.admin_account": "Apertez jerintervizajo por @{name}", "status.admin_domain": "Apertez jerintervizajo por {domain}", "status.admin_status": "Open this status in the moderation interface", - "status.all_disabled": "Dishavigi e citi esas desaktivigita", + "status.all_disabled": "Diskonocigi e citi esas desaktivigita", "status.block": "Restriktez @{name}", "status.bookmark": "Lektosigno", "status.cancel_reblog_private": "Desrepetez", @@ -861,10 +956,11 @@ "status.quotes_count": "{count, plural,one {{counter} cito} other {{counter} citi}}", "status.read_more": "Lektez plu", "status.reblog": "Repetez", - "status.reblog_or_quote": "Dishavigez o citez", + "status.reblog_or_quote": "Diskonocez o citez", "status.reblog_private": "Ankorfoye kunhavigez kun vua sequanti", "status.reblogged_by": "{name} repetis", "status.reblogs.empty": "Nulu ja repetis ca posto. Kande ulu facas lo, lu montresos hike.", + "status.reblogs_count": "{count, plural,one {{counter} diskonoco} other {{counter} diskonoci}}", "status.redraft": "Efacez e riskisigez", "status.remove_bookmark": "Forigar lektosigno", "status.remove_favourite": "Forigar de priziti", @@ -891,7 +987,10 @@ "subscribed_languages.save": "Sparez chanji", "subscribed_languages.target": "Chanjez abonita lingui por {target}", "tabs_bar.home": "Hemo", + "tabs_bar.menu": "Menuo", "tabs_bar.notifications": "Savigi", + "tabs_bar.publish": "Nova afisho", + "tabs_bar.search": "Serchez", "terms_of_service.title": "Servtermini", "time_remaining.days": "{number, plural, one {# dio} other {# dii}} restas", "time_remaining.hours": "{number, plural, one {# horo} other {# hori}} restas", @@ -929,9 +1028,12 @@ "visibility_modal.direct_quote_warning.text": "Se vu retenas la nuna preferaji, la enpozita cito esas chanjita ad ligilo.", "visibility_modal.direct_quote_warning.title": "Citi ne povas esar enpozita en privata mencioni", "visibility_modal.helper.direct_quoting": "Privata mencioni quan esas igita che Mastodon ne povas mencionita da altri.", + "visibility_modal.helper.privacy_editing": "Videbleso ne povas esar chanjita pos afisho esar publikigita.", "visibility_modal.helper.privacy_private_self_quote": "Suciti di privata afishi ne povas esar publikigita.", "visibility_modal.helper.private_quoting": "Nursequanta afishi quan igita che Mastodon ne povas esar citita da altri.", "visibility_modal.helper.unlisted_quoting": "Kande personi citas vu, anke lia afisho esar celitos de tendenca tempolinei.", + "visibility_modal.instructions": "Kontrolez qua povas interagar kun ca afisho. Vu povas irar ad Preferaji > Afishigpredeterminitaji.", + "visibility_modal.privacy_label": "Videbleso", "visibility_modal.quote_followers": "Nur sequanti", "visibility_modal.quote_label": "Qua povas citar", "visibility_modal.quote_nobody": "Nur me", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 4e225422c60..9b2689f09d4 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -125,6 +125,7 @@ "annual_report.summary.followers.new_followers": "{count, plural, other {新しいフォロワー}}", "annual_report.summary.highlighted_post.boost_count": "この投稿は {count, plural, other {# 回}}ブーストされました。", "annual_report.summary.highlighted_post.favourite_count": "この投稿は {count, plural, other {# 回}}お気に入りされました。", + "annual_report.summary.highlighted_post.reply_count": "この投稿には {count, plural, other {# 回}}返信がありました。", "annual_report.summary.highlighted_post.title": "最も人気のある投稿", "annual_report.summary.most_used_app.most_used_app": "最も使用されているアプリ", "annual_report.summary.most_used_hashtag.most_used_hashtag": "最も使用されたハッシュタグ", diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json index 12c3363a984..1554f46cc70 100644 --- a/app/javascript/mastodon/locales/tt.json +++ b/app/javascript/mastodon/locales/tt.json @@ -1,6 +1,7 @@ { "about.blocks": "Модерациялана торган серверлар", "about.contact": "Бәйләнеш:", + "about.default_locale": "Килешү буенча", "about.disclaimer": "Mastodon-бушлай ачык чыганак программасы һәм Mastodon gmbh сәүдә маркасы.", "about.domain_blocks.no_reason_available": "Сәбәбе юк", "about.domain_blocks.preamble": "Mastodon гадәттә сезгә бүтән fediverse серверыннан эчтәлекне карарга һәм аның белән кулланучылар белән аралашырга мөмкинлек бирә. Бу конкрет серверда ясалган искәрмәләр.", @@ -12,17 +13,28 @@ "about.not_available": "Бу серверда бу мәгълүмат юк иде.", "about.powered_by": "{mastodon} нигезендә үзәкчелеге бетерелгән социаль челтәр нигезендә", "about.rules": "Сервер кагыйдәләре", + "account.account_note_header": "Шәхси искәрмәләр", "account.activity": "Активлык", + "account.add_note": "Шәхси искәрмә өстәү", "account.add_or_remove_from_list": "Исемлеккә кушу яки бетерү", + "account.badges.admin": "Админ", + "account.badges.blocked": "Блокланган", "account.badges.bot": "Бот", + "account.badges.domain_blocked": "Блокланган доменнар", "account.badges.group": "Төркем", + "account.badges.muted": "Тавышсыз", + "account.badges.muted_until": "{until}-га кадәр игнорлау", "account.block": "@{name} кулланучыны блоклау", "account.block_domain": "{domain} доменын блоклау", "account.block_short": "Блокла", "account.blocked": "Блокланган", + "account.blocking": "Блокланган", "account.cancel_follow_request": "Киләсе сорау", "account.copy": "Профиль сылтамасын күчереп ал", + "account.direct": "{name}-ны шәхсән икә алу", "account.disable_notifications": "@{name} язулары өчен белдерүләр сүндерү", + "account.domain_blocking": "Блокланган домен", + "account.edit_note": "Шәхси искәрмәне төзәтү", "account.edit_profile": "Профильне үзгәртү", "account.edit_profile_short": "Үзгәрт", "account.enable_notifications": "@{name} язулары өчен белдерүләр яндыру", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 03ca0e77b06..b106d92362f 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -1071,7 +1071,7 @@ "remove_quote_hint.button_label": "Đã hiểu", "remove_quote_hint.message": "Bạn cũng có thể làm trong menu tùy chọn {icon}", "remove_quote_hint.title": "Gỡ tút mà bạn đã trích dẫn?", - "reply_indicator.attachments": "{count, plural, other {# tập tin đính kèm}}", + "reply_indicator.attachments": "{count, plural, other {# đính kèm}}", "reply_indicator.cancel": "Hủy bỏ", "reply_indicator.poll": "Vốt", "report.block": "Chặn", @@ -1283,14 +1283,14 @@ "units.short.thousand": "{count}K", "upload_area.title": "Kéo và thả để tải lên", "upload_button.label": "Thêm media (JPEG, PNG, GIF, WebM, MP4, MOV)", - "upload_error.limit": "Tập tin tải lên vượt quá giới hạn cho phép.", - "upload_error.poll": "Không cho phép đính kèm tập tin.", - "upload_error.quote": "Không cho phép đính kèm tập tin với trích dẫn.", - "upload_form.drag_and_drop.instructions": "Để chọn tập tin đính kèm, hãy nhấn phím cách hoặc phím Enter. Trong khi kéo, sử dụng các phím mũi tên để di chuyển tập tin đính kèm theo bất kỳ hướng nào. Nhấn phím cách hoặc phím Enter một lần nữa để thả tập tin đính kèm vào vị trí mới hoặc nhấn phím thoát để hủy.", - "upload_form.drag_and_drop.on_drag_cancel": "Kéo thả đã bị hủy bỏ. Tập tin đính kèm {item} bị bỏ qua.", - "upload_form.drag_and_drop.on_drag_end": "Tập tin đính kèm {item} bị bỏ qua.", - "upload_form.drag_and_drop.on_drag_over": "Tập tin đính kèm {item} đã bị dời.", - "upload_form.drag_and_drop.on_drag_start": "Đã chọn tập tin đính kèm {item}.", + "upload_error.limit": "Tệp tải lên vượt quá giới hạn cho phép.", + "upload_error.poll": "Không cho phép đính kèm tệp.", + "upload_error.quote": "Không cho phép đính kèm tệp với trích dẫn.", + "upload_form.drag_and_drop.instructions": "Để chọn tệp đính kèm, hãy nhấn phím cách hoặc phím Enter. Trong khi kéo, sử dụng các phím mũi tên để di chuyển tệp đính kèm theo bất kỳ hướng nào. Nhấn phím cách hoặc phím Enter một lần nữa để thả tệp đính kèm vào vị trí mới hoặc nhấn phím thoát để hủy.", + "upload_form.drag_and_drop.on_drag_cancel": "Kéo thả đã bị hủy bỏ. Tệp đính kèm {item} bị bỏ qua.", + "upload_form.drag_and_drop.on_drag_end": "Tệp đính kèm {item} bị bỏ qua.", + "upload_form.drag_and_drop.on_drag_over": "Tệp đính kèm {item} bị di chuyển.", + "upload_form.drag_and_drop.on_drag_start": "Đã chọn tệp đính kèm {item}.", "upload_form.edit": "Biên tập", "upload_progress.label": "Đang tải lên...", "upload_progress.processing": "Đang tải lên…", diff --git a/config/locales/be.yml b/config/locales/be.yml index d7035d5c266..9c3c944c34b 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -2242,7 +2242,7 @@ be: details: 'Вось падрабязнасці ўваходу:' explanation: Мы заўважылі ўваход у ваш уліковы запіс з новага IP-адрасу. further_actions_html: Калі гэта былі не вы, раім вам неадкладна %{action}, а таксама ўключыць двухфактарную аўтэнтыфікацыю, каб захаваць бяспеку вашага ўліковага запісу. - subject: У вас уліковы запіс зайшлі з новага IP-адрасу + subject: У Ваш уліковы запіс зайшлі з новага IP-адрасу title: Новы ўваход terms_of_service_changed: agreement: Працягваючы карыстацца %{domain}, Вы пагаджаецеся з гэтымі ўмовамі. Калі Вы не згодныя з абноўленымі ўмовамі, то можаце ў любы момант адмовіцца ад пагаднення з %{domain}, выдаліўшы свой профіль. diff --git a/config/locales/doorkeeper.gd.yml b/config/locales/doorkeeper.gd.yml index 6487669c283..2696672e65a 100644 --- a/config/locales/doorkeeper.gd.yml +++ b/config/locales/doorkeeper.gd.yml @@ -83,6 +83,10 @@ gd: access_denied: Dhiùlt sealbhadair a’ ghoireis no am frithealaiche ùghdarrachaidh an t-iarrtas. credential_flow_not_configured: Dh’fhàillig le sruth cruthachadh teisteas facail-fhaire do shealbhadair a’ ghoireis ri linn Doorkeeper.configure.resource_owner_from_credentials gun rèiteachadh. invalid_client: Dh’fhàillig le dearbhadh a’ chliant ri linn cliant nach aithne dhuinn, dearbhadh cliant nach deach gabhail a-staigh no dòigh dearbhaidh ris nach cuirear taic. + invalid_code_challenge_method: + one: Feumaidh an code_challenge_method a bhith ’na %{challenge_methods}. + other: Feumaidh an code_challenge_method a bhith aon de %{challenge_methods}. + zero: Cha chuir frithealaiche an ùghdarrachaidh taic ri PKCE air sgàth ’s nach eil luach code_challenge_method ann ris an gabhar. invalid_grant: Chan eil an t-ùghdarrachadh a chaidh a thoirt seachad dligheach, dh’fhalbh an ùine air, chaidh a chùl-ghairm no chan eil e a-rèir URI an ath-stiùiridh a chaidh a chleachdadh san iarrtas ùghdarrachaidh no chaidh fhoillseachadh le cliant eile. invalid_redirect_uri: Chan eil an URI ath-stiùiridh a chaidh a ghabhail a-staigh dligheach. invalid_request: diff --git a/config/locales/doorkeeper.vi.yml b/config/locales/doorkeeper.vi.yml index af609bfa71a..5f980c85911 100644 --- a/config/locales/doorkeeper.vi.yml +++ b/config/locales/doorkeeper.vi.yml @@ -136,7 +136,7 @@ vi: follow: Theo dõi, Phớt lờ và Chặn follows: Đang theo dõi lists: Danh sách - media: Tập tin đính kèm + media: Tệp đính kèm mutes: Đã phớt lờ notifications: Thông báo profile: Hồ sơ Mastodon của bạn @@ -194,7 +194,7 @@ vi: write:filters: tạo bộ lọc write:follows: theo dõi ai đó write:lists: tạo danh sách - write:media: tải lên tập tin + write:media: tải lên tệp write:mutes: phớt lờ tài khoản và thảo luận write:notifications: xóa thông báo write:reports: báo cáo diff --git a/config/locales/el.yml b/config/locales/el.yml index df67698672c..41fa094dfaf 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -1218,7 +1218,7 @@ el: created_msg: Δημιουργήθηκε νέο ψευδώνυμο. Τώρα μπορείς να ξεκινήσεις τη μεταφορά από τον παλιό λογαριασμό. deleted_msg: Αφαιρέθηκε το ψευδώνυμο. Η μεταφορά από εκείνον τον λογαριασμό σε αυτόν εδώ δε θα είναι πλέον δυνατή. empty: Δεν έχεις ψευδώνυμα. - hint_html: Αν θέλεις να μετακινηθείς από έναν άλλο λογαριασμό σε αυτόν εδώ, εδώ μπορείς να δημιουργήσεις ένα ψευδώνυμο, πράγμα που απαιτείται πριν προχωρήσεις για να μεταφέρεις τους ακολούθους σου από τον παλιό λογαριασμό σε αυτόν εδώ. Η ενέργεια αυτή είναι ακίνδυνη και αναστρέψιμη.Η μετακόμιση του λογαριασμού ξεκινάει από τον παλιό λογαριασμό. + hint_html: Αν θέλεις να μετακινηθείς από έναν άλλο λογαριασμό σε αυτόν εδώ, εδώ μπορείς να δημιουργήσεις ένα ψευδώνυμο, το οποίο απαιτείται πριν προχωρήσεις για να μεταφέρεις τους ακολούθους σου από τον παλιό λογαριασμό σε αυτόν εδώ. Η ενέργεια αυτή είναι ακίνδυνη και αναστρέψιμη. Η μετακίνηση του λογαριασμού ξεκινάει από τον παλιό λογαριασμό. remove: Αποσύνδεση ψευδώνυμου appearance: advanced_settings: Προχωρημένες ρυθμίσεις @@ -1263,7 +1263,7 @@ el: welcome_title: Καλώς ήρθες, %{name}! wrong_email_hint: Εάν αυτή η διεύθυνση email δεν είναι σωστή, μπορείς να την αλλάξεις στις ρυθμίσεις λογαριασμού. delete_account: Διαγραφή λογαριασμού - delete_account_html: Αν θέλεις να διαγράψεις το λογαριασμό σου, μπορείς να συνεχίσεις εδώ. Θα σου ζητηθεί επιβεβαίωση. + delete_account_html: Αν θέλεις να διαγράψεις το λογαριασμό σου, μπορείς να προχωρήσεις εδώ. Θα σου ζητηθεί επιβεβαίωση. description: prefix_invited_by_user: Ο/Η @%{name} σε προσκαλεί να γίνεις μέλος αυτού του διακομιστή του Mastodon! prefix_sign_up: Κάνε εγγραφή στο Mastodon σήμερα! @@ -1278,7 +1278,7 @@ el: login: Σύνδεση logout: Αποσύνδεση migrate_account: Μεταφορά σε διαφορετικό λογαριασμό - migrate_account_html: Αν θέλεις να ανακατευθύνεις αυτό τον λογαριασμό σε έναν διαφορετικό, μπορείς να το διαμορφώσεις εδώ. + migrate_account_html: Αν θέλεις να ανακατευθύνεις αυτόν το λογαριασμό σε έναν διαφορετικό, μπορείς να το διαμορφώσεις εδώ. or_log_in_with: Ή συνδέσου με progress: confirm: Επιβεβαίωση email @@ -1372,18 +1372,18 @@ el: x_seconds: "%{count}δ" deletes: challenge_not_passed: Οι πληροφορίες που εισήγαγες δεν ήταν σωστές - confirm_password: Γράψε το τρέχον συνθηματικό σου για να πιστοποιήσεις την ταυτότητά σου + confirm_password: Γράψε το τρέχον συνθηματικό σου για να επαληθεύσεις την ταυτότητά σου confirm_username: Γράψε το όνομα χρήστη σου για επιβεβαίωση proceed: Διαγραφή λογαριασμού success_msg: Ο λογαριασμός σου διαγράφηκε με επιτυχία warning: - before: 'Πριν συνεχίσεις, παρακαλούμε να διαβάσεις τις παρακάτω σημειώσεις προσεκτικά:' + before: 'Πριν συνεχίσεις, παρακαλούμε διάβασε αυτές τις σημειώσεις προσεκτικά:' caches: Περιεχόμενο που έχει αποθηκευτεί προσωρινά σε άλλους διακομιστές ίσως παραμείνει data_removal: Οι αναρτήσεις σου και άλλα δεδομένα θα διαγραφούν οριστικά email_change_html: Μπορείς να αλλάξεις τη διεύθυνση email σου χωρίς να διαγράψεις το λογαριασμό σου - email_contact_html: Αν και πάλι δεν εμφανιστεί, μπορείς να στείλεις email στο %{email} για βοήθεια - email_reconfirmation_html: Αν δεν έχεις λάβει το email επιβεβαίωσης, μπορείς να το ζητήσεις ξανά - irreversible: Δεν θα μπορείς να ανακτήσεις ή ενεργοποιήσεις ξανά το λογαριασμό σου + email_contact_html: Αν και πάλι δεν φτάσει, μπορείς να στείλεις email στο %{email} για βοήθεια + email_reconfirmation_html: Αν δεν λαμβάνεις το email επιβεβαίωσης, μπορείς να το ζητήσεις ξανά + irreversible: Δεν θα μπορείς να επαναφέρεις ή ενεργοποιήσεις ξανά το λογαριασμό σου more_details_html: Για περισσότερες πληροφορίες, δες την πολιτική απορρήτου. username_available: Το όνομα χρήστη σου θα γίνει ξανά διαθέσιμο username_unavailable: Το όνομα χρήστη σου θα παραμείνει μη διαθέσιμο @@ -1482,7 +1482,7 @@ el: archive_takeout: date: Ημερομηνία download: Κατέβασε το αρχείο σου - hint_html: Μπορείς να αιτηθείς ένα αρχείο των αναρτήσεων και των ανεβασμένων πολυμέσων σου. Τα δεδομένα θα είναι σε μορφή ActivityPub, προσπελάσιμα από οποιοδήποτε συμβατό πρόγραμμα. Μπορείς να αιτηθείς ένα αρχείο κάθε 7 μέρες. + hint_html: Μπορείς να αιτηθείς ένα αρχείο των αναρτήσεων και ανεβασμένων πολυμέσων σου. Τα δεδομένα θα είναι σε μορφή ActivityPub, προσπελάσιμα από οποιοδήποτε συμβατό πρόγραμμα. Μπορείς να αιτηθείς ένα αρχείο κάθε 7 μέρες. in_progress: Συγκεντρώνουμε το αρχείο σου... request: Αιτήσου το αρχείο σου size: Μέγεθος @@ -1668,7 +1668,7 @@ el: table: expires_at: Λήγει uses: Χρήσεις - title: Προσκάλεσε κόσμο + title: Προσκάλεσε άτομα link_preview: author_html: Από %{name} potentially_sensitive_content: @@ -1706,26 +1706,26 @@ el: missing_also_known_as: δεν είναι ψευδώνυμο αυτού του λογαριασμού move_to_self: δεν μπορεί να είναι ο τρέχων λογαριασμός not_found: δεν βρέθηκε - on_cooldown: Είσαι σε περίοδο ηρεμίας + on_cooldown: Είσαι σε περίοδο αναμονής followers_count: Ακόλουθοι τη στιγμή της μεταφοράς incoming_migrations: Μεταφορά από διαφορετικό λογαριασμό - incoming_migrations_html: Για να μετακομίσεις από έναν άλλο λογαριασμό σε αυτόν εδώ, πρώτα πρέπει να δημιουργήσεις ένα ψευδώνυμο λογαριασμού. - moved_msg: Ο λογαριασμός σου πλέον ανακατευθύνεται στον %{acct} και οι ακόλουθοί σου μεταφέρονται εκεί. + incoming_migrations_html: Για να μεταφερθείς από έναν άλλο λογαριασμό σε αυτόν εδώ, πρώτα πρέπει να δημιουργήσεις ένα ψευδώνυμο λογαριασμού. + moved_msg: Ο λογαριασμός σου τώρα ανακατευθύνεται στο %{acct} και οι ακόλουθοί σου μεταφέρονται εκεί. not_redirecting: Ο λογαριασμός σου δεν ανακατευθύνεται σε κανέναν άλλο προς το παρόν. - on_cooldown: Έχεις μετακομίσει το λογαριασμό σου πρόσφατα. Η δυνατότητα αυτή θα γίνει πάλι διαθέσιμη σε %{count} μέρες. + on_cooldown: Έχεις μετακινήσει τον λογαριασμό σου πρόσφατα. Η δυνατότητα αυτή θα γίνει πάλι διαθέσιμη σε %{count} μέρες. past_migrations: Προηγούμενες μετακινήσεις - proceed_with_move: Μετακίνηση ακολούθων - redirected_msg: Ο λογαριασμός σου ανακατευθύνεται στον %{acct}. - redirecting_to: Ο λογαριασμός σου ανακατευθύνεται στον %{acct}. + proceed_with_move: Μεταφορά ακολούθων + redirected_msg: Ο λογαριασμός σου τώρα ανακατευθύνεται στο %{acct}. + redirecting_to: Ο λογαριασμός σου ανακατευθύνεται στο %{acct}. set_redirect: Όρισε ανακατεύθυνση warning: backreference_required: Θα πρέπει πρώτα να ρυθμιστεί μια παραπομπή από τον νέο λογαριασμό προς αυτόν before: 'Πριν συνεχίσεις, παρακαλούμε διάβασε αυτές τις σημειώσεις προσεκτικά:' cooldown: Μετά τη μετακίνηση υπάρχει μια περίοδος αναμονής κατά τη διάρκεια της οποίας δεν θα είσαι σε θέση να μετακινηθείς ξανά - disabled_account: Ο τρέχων λογαριασμός σου δε θα είναι πλήρως ενεργός μετά. Πάντως θα έχεις πρόσβαση στην εξαγωγή δεδομένων καθώς και στην επανενεργοποίηση. + disabled_account: Ο τρέχων λογαριασμός σου δε θα είναι πλήρως χρησιμοποιήσιμος μετά. Πάντως, θα έχεις πρόσβαση στην εξαγωγή δεδομένων καθώς και στην επανενεργοποίηση. followers: Αυτή η ενέργεια θα μεταφέρει όλους τους ακόλουθούς σου από τον τρέχοντα λογαριασμό στον νέο λογαριασμό only_redirect_html: Εναλλακτικά, μπορείς απλά να προσθέσεις μια ανακατατεύθυνση στο προφίλ σου. - other_data: Δεν θα μετακινηθούν αυτόματα άλλα δεδομένα (συμπεριλαμβανομένου των αναρτήσεων σας και της λίστας των λογαριασμών που ακολουθείτε) + other_data: Δεν θα μετακινηθούν αυτόματα άλλα δεδομένα (συμπεριλαμβανομένου των αναρτήσεων σου και της λίστας των λογαριασμών που ακολουθείς) redirect: Το προφίλ του τρέχοντος λογαριασμού σου θα ενημερωθεί με μια σημείωση ανακατεύθυνσης και θα εξαιρεθεί από τα αποτελέσματα αναζητήσεων moderation: title: Συντονισμός @@ -1944,7 +1944,7 @@ el: featured_tags: Αναδεδειγμένες ετικέτες import: Εισαγωγή import_and_export: Εισαγωγή και εξαγωγή - migrate: Μετακόμιση λογαριασμού + migrate: Μετακίνηση λογαριασμού notifications: Ειδοποιήσεις μέσω email preferences: Προτιμήσεις profile: Προφίλ @@ -2238,7 +2238,7 @@ el: signed_in_as: 'Έχεις συνδεθεί ως:' verification: extra_instructions_html: Συμβουλή: Ο σύνδεσμος στην ιστοσελίδα σου μπορεί να είναι αόρατος. Το σημαντικό μέρος είναι το rel="me" που αποτρέπει την μίμηση σε ιστοσελίδες με περιεχόμενο παραγόμενο από χρήστες. Μπορείς ακόμα να χρησιμοποιήσεις μια ετικέτα link στην κεφαλίδα της σελίδας αντί για a, αλλά η HTML πρέπει να είναι προσβάσιμη χωρίς την εκτέλεση JavaScript. - here_is_how: Δείτε πώς + here_is_how: Ορίστε πώς hint_html: Η επαλήθευση της ταυτότητας στο Mastodon είναι για όλους. Βασισμένο σε ανοιχτά πρότυπα ιστού, τώρα και για πάντα δωρεάν. Το μόνο που χρειάζεσαι είναι μια προσωπική ιστοσελίδα που ο κόσμος να σε αναγνωρίζει από αυτή. Όταν βάζεις σύνδεσμο προς αυτήν την ιστοσελίδα από το προφίλ σου, θα ελέγξουμε ότι η ιστοσελίδα συνδέει πίσω στο προφίλ σου και θα δείξουμε μια οπτική ένδειξη σε αυτό. instructions_html: Αντέγραψε και επικόλλησε τον παρακάτω κώδικα στην HTML της ιστοσελίδας σου. Στη συνέχεια, πρόσθεσε τη διεύθυνση της ιστοσελίδας σου σε ένα από τα επιπλέον πεδία στο προφίλ σου από την καρτέλα "Επεξεργασία προφίλ" και αποθήκευσε τις αλλαγές. verification: Επαλήθευση diff --git a/config/locales/gd.yml b/config/locales/gd.yml index 7eaf6a98523..9c363fb1856 100644 --- a/config/locales/gd.yml +++ b/config/locales/gd.yml @@ -14,6 +14,11 @@ gd: one: Neach-leantainn other: Luchd-leantainn two: Luchd-leantainn + following: + few: "’Gan leantainn" + one: "’Ga leantainn" + other: "’Gan leantainn" + two: "’Gan leantainn" instance_actor_flash: "’S e actar biortail a tha sa chunntas seo a riochdaicheas am frithealaiche fhèin seach cleachdaiche sònraichte. Tha e ’ga chleachdadh a chùm co-nasgaidh agus cha bu chòir dhut a chur à rèim." last_active: an gnìomh mu dheireadh link_verified_on: Chaidh dearbhadh cò leis a tha an ceangal seo %{date} @@ -57,6 +62,7 @@ gd: label: Atharraich an dreuchd no_role: Gun dreuchd title: Atharraich an dreuchd aig %{username} + collections: Cruinneachaidhean confirm: Dearbh confirmed: Chaidh a dhearbhachadh confirming: "’Ga dhearbhadh" @@ -269,6 +275,7 @@ gd: demote_user_html: Dh’ìslich %{name} an cleachdaiche %{target} destroy_announcement_html: Sguab %{name} às am brath-fios %{target} destroy_canonical_email_block_html: Dhì-bhac %{name} am post-d air a bheil an hais %{target} + destroy_collection_html: Thug %{name} cruinneachadh aig %{target} air falbh destroy_custom_emoji_html: Sguab %{name} às an Emoji %{target} destroy_domain_allow_html: Dì-cheadaich %{name} co-nasgadh leis an àrainn %{target} destroy_domain_block_html: Dì-bhac %{name} an àrainn %{target} @@ -308,6 +315,7 @@ gd: unsilence_account_html: Dì-chuingich %{name} an cunntas aig %{target} unsuspend_account_html: Chuir %{name} an cunntas aig %{target} ann an rèim a-rithist update_announcement_html: Dh’ùraich %{name} am brath-fios %{target} + update_collection_html: Dh’ùraich %{name} cruinneachadh le %{target} update_custom_emoji_html: Dh’ùraich %{name} an Emoji %{target} update_domain_block_html: Dh’ùraich %{name} bacadh na h-àrainne %{target} update_ip_block_html: Sguab %{name} às riaghailt dhan IP %{target} @@ -343,6 +351,17 @@ gd: unpublish: Neo-fhoillsich unpublished_msg: Chaidh am brath-fios a dhì-fhoillseachadh! updated_msg: Chaidh am brath-fios ùrachadh! + collections: + accounts: Cunntasan + collection_title: Cruinneachadh le %{name} + contents: Susbaint + number_of_accounts: + few: "%{count} cunntasan" + one: "%{count} chunntas" + other: "%{count} cunntas" + two: "%{count} chunntas" + open: Fosgail + view_publicly: Seall gu poblach critical_update_pending: Ùrachadh èiginneach ri dhèiligeadh custom_emojis: assign_category: Iomruin roinn-seòrsa dha @@ -700,6 +719,7 @@ gd: cancel: Sguir dheth category: Roinn-seòrsa category_description_html: Thèid iomradh a thoirt air adhbhar a’ ghearain mun chunntas/susbaint seo sa chonaltradh leis a’ chunntas mun a chaidh an gearan a thogail + collections: Cruinneachaidhean (%{count}) comment: none: Chan eil gin comment_description_html: 'Airson barrachd fiosrachaidh a sholar, sgrìobh %{name}:' @@ -729,11 +749,13 @@ gd: report: 'Gearan air #%{id}' reported_account: Cunntas mun a chaidh a ghearan reported_by: Chaidh gearan a dhèanamh le + reported_content: Susbaint a’ ghearain reported_with_application: Chaidh an gearan a dhèanamh le aplacaid resolved: Air fhuasgladh resolved_msg: Chaidh an gearan fhuasgladh! skip_to_actions: Geàrr leum dha na gnìomhan status: Staid + statuses: Postaichean (%{count}) statuses_description_html: Thèid iomradh a thoirt air an t-susbaint oilbheumach sa chonaltradh leis a’ chunntas mun a chaidh an gearan a thogail summary: action_preambles: @@ -770,6 +792,7 @@ gd: categories: administration: Rianachd devops: DevOps + email: Post-d invites: Cuiridhean moderation: Maorsainneachd special: Sònraichte @@ -788,6 +811,7 @@ gd: administrator_description: Chan eil cuingeachadh sam bith air na cleachdaichean aig bheil an cead seo delete_user_data: Sguabadh às dàta cleachdaiche delete_user_data_description: Leigidh seo le cleachdaichean dàta chleachdaichean eile a sguabadh às gun dàil + invite_bypass_approval: Thoir cuireadh do chleachdaichean gun lèirmheas invite_users: Thoir cuireadh do chleachdaichean invite_users_description: Leigidh seo le cleachdaichean cuireadh dhan fhrithealaiche a chur gu daoine eile manage_announcements: Stiùireadh nam brathan-fios @@ -798,6 +822,8 @@ gd: manage_blocks_description: Leigidh seo le cleachdaichean solaraichean puist-d is seòlaidhean IP a bhacadh manage_custom_emojis: Stiùireadh nan Emojis gnàthaichte manage_custom_emojis_description: Leigidh seo le cleachdaichean Emojis gnàthaichte a stiùireadh air an fhrithealaiche + manage_email_subscriptions: Stiùireadh fo-sgrìobhaidhean puist-d + manage_email_subscriptions_description: Leigidh seo le cleachdaichean fo-sgrìobhadh air a’ phost-d a dhèanamh air na cleachdaichean aig a bheil an cead seo manage_federation: Stiùireadh a’ cho-nasgaidh manage_federation_description: Leigidh seo le cleachdaichean an co-nasgadh le àrainnean eile a bhacadh no a cheadachadh agus stiùireadh dè ghabhas lìbhrigeadh manage_invites: Stiùireadh nan cuiridhean @@ -826,6 +852,7 @@ gd: view_devops_description: Leigidh seo le cleachdaichean na deas-bhùird aig Sidekiq is pgHero inntrigeadh view_feeds: Seall loidhnichean-ama beòtha ’s nan cuspairean view_feeds_description: Leigidh seo le cleachdaichean loidhnichean-ama beòtha ’s nan cuspairean inntrigeadh ge b’ e dè roghainnean an fhrithealaiche + requires_2fa: Feumaidh seo dearbhadh dà-cheumnach title: Dreuchdan rules: add_new: Cuir riaghailt ris @@ -1297,6 +1324,7 @@ gd: progress: confirm: Dearbh am post-d details: Am fiosrachadh agad + list: Adhartas a’ chlàraidh review: An lèirmheas againn rules: Gabh ris na riaghailtean providers: @@ -1312,6 +1340,7 @@ gd: invited_by: "’S urrainn dhut ballrachd fhaighinn air %{domain} leis a’ chuireadh a fhuair thu o:" preamble: Tha iad ’gan stèidheachadh is a chur an gnìomh leis na maoir aig %{domain}. preamble_invited: Mus lean thu air adhart, thoir an aire air na riaghailtean a shuidhich na maoir aig %{domain}. + read_more: Leugh an còrr title: Riaghailtean bunasach. title_invited: Fhuair thu cuireadh. security: Tèarainteachd @@ -1433,6 +1462,27 @@ gd: basic_information: Fiosrachadh bunasach hint_html: "Gnàthaich na chithear air a’ phròifil phoblach agad is ri taobh nam postaichean agad. Bidh càch nas buailtiche do leantainn agus conaltradh leat nuair a bhios tu air a’ phròifil agad a lìonadh agus dealbh rithe." other: Eile + redesign_body: "’S urrainn dhut a’ phròifil agad a dheasachadh air duilleag na pròifile fhèin." + redesign_button: Tadhail air + email_subscription_mailer: + confirmation: + action: Dearbh an seòladh puist-d + instructions_to_confirm: Dearbh gum bu mhiann leat puist-d fhaighinn o %{name} (@%{acct}) nuair a dh’fhoillsicheas iad postaichean ùra. + instructions_to_ignore: Mur eil thu cinnteach carson a fhuair thu am post-d seo, ’s urrainn dhut a sguabadh às. Cha dèid d’ fho-sgrìobhadh mura bhriog thu air a’ cheangal gu h-àrd. + subject: Dearbh an seòladh puist-d agad + title: A bheil thu airson naidheachdan %{name} fhaighinn air a’ phost-d? + notification: + create_account: Cruthaich cunntas Mastodon + email_subscriptions: + active: Gnìomhach + confirmations: + show: + changed_your_mind: Na chuir thu romhad a chaochladh? + title: Tha thu air clàradh + unsubscribe: Cuir crìoch air an fho-sgrìobhadh + inactive: Neo-ghnìomhach + status: Staid + subscribers: Fo-sgrìobhaichean emoji_styles: auto: Fèin-obrachail native: Tùsail @@ -1844,6 +1894,7 @@ gd: posting_defaults: Bun-roghainnean a’ phostaidh public_timelines: Loidhnichean-ama poblach privacy: + email_subscriptions: Cuir postaichean air a’ phost-d hint_html: "Gnàthaich an dòigh air an dèid a’ phròifil ’s na postaichean agad a lorg. Tha grunn ghleusan aig Mastodon a chuidicheas ach an ruig thu èisteachd nas fharsainge nuair a bhios iad an comas. Thoir sùil air na roghainnean seo a dhèanamh cinnteach gum freagair iad ri d’ fheumalachdan." privacy: Prìobhaideachd privacy_hint_html: Stiùirich na tha thu airson foillseachadh do chàch. Gheibh daoine lorg air pròifilean inntinneach is deagh aplacaidean a’ brabhsadh cò tha daoine eile a’ leantainn ’s a’ faicinn nan aplacaidean a chleachdas iad airson postadh ach dh’fhaoidte gum b’ fheàrr leat seo a chumail falaichte. @@ -2110,7 +2161,30 @@ gd: recovery_codes: Còdan aiseig ’nan lethbhreac-glèidhidh recovery_codes_regenerated: Chaidh na còdan aiseig ath-ghintinn recovery_instructions_html: Ma chailleas tu an t-inntrigeadh dhan fhòn agad, ’s urrainn dhut fear dhe na còdan aisig gu h-ìosal a chleachdadh airson faighinn a-steach dhan chunntas agad a-rithist. Cùm na còdan aisig sàbhailte. Mar eisimpleir, ’s urrainn dhut an clò-bhualadh ’s a chumail far a bheil thu a’ cumail na sgrìobhainnean cudromach eile agad. + resume_app_authorization: Lean air ùghdarrachadh na h-aplacaid webauthn: Iuchraichean tèarainteachd + unsubscriptions: + create: + action: Tadhail air duilleag-dhachaigh an fhrithealaiche + email_subscription: + confirmation_html: Chan fhaigh thu post-d o %{name} tuilleadh. + title: Chaidh crìoch a chur air an fho-sgrìobhadh agad + user: + confirmation_html: Chan fhaigh thu %{type} o Mhastodon air %{domain} tuilleadh. + notification_emails: + favourite: puist-d le brathan mu annsachdan + follow: puist-d le brathan mu leantainn + follow_request: puist-d le brathan mu iarrtasan leantainn + mention: puist-d le brathan mu iomraidhean + reblog: puist-d le brathan mu bhrosnachaidhean + show: + action: Cuir crìoch air an fho-sgrìobhadh + email_subscription: + confirmation_html: Chan fhaigh thu post-d tuilleadh nuair a dh’fhoillsicheas an cunntas seo postaichean ùra. + title: A bheil thu airson crìoch a chur air an fho-sgrìobhadh agad air %{name}? + user: + confirmation_html: Chan fhaigh thu %{type} o Mhastodon air %{domain} tuilleadh. + title: A bheil thu airson crìoch a chur air an fho-sgrìobhadh agad air %{type}? user_mailer: announcement_published: description: 'Tha na rianairean aig %{domain} a’ dèanamh brath-fios:' diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index 7a7e929ae11..9f369ab86dc 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -39,7 +39,7 @@ el: appeal: text: Μπορείς να κάνετε έφεση σε ένα παράπτωμα μόνο μία φορά defaults: - autofollow: Όσοι εγγραφούν μέσω της πρόσκλησης θα σε ακολουθούν αυτόματα + autofollow: Όσοι εγγραφούν μέσω της πρόσκλησης θα σε ακολουθήσουν αυτόματα avatar: WEBP, PNG, GIF ή JPG. Το πολύ %{size}. Θα υποβαθμιστεί σε %{dimensions}px bot: Υποδεικνύει σε άλλους χρήστες ότι ο λογαριασμός αυτός εκτελεί κυρίως αυτοματοποιημένες ενέργειες και ίσως να μην παρακολουθείται context: Ένα ή περισσότερα πλαίσια στα οποία μπορεί να εφαρμόζεται αυτό το φίλτρο @@ -212,7 +212,7 @@ el: appeal: text: Εξηγήστε γιατί αυτή η απόφαση πρέπει να αντιστραφεί defaults: - autofollow: Προσκάλεσε για να ακολουθήσουν το λογαριασμό σου + autofollow: Προσκάλεσε να ακολουθήσουν τον λογαριασμό σου avatar: Εικόνα προφίλ bot: Αυτός είναι ένας αυτοματοποιημένος λογαριασμός (bot) chosen_languages: Φιλτράρισμα γλωσσών diff --git a/config/locales/simple_form.gd.yml b/config/locales/simple_form.gd.yml index edbd86c9d79..85cca6346e2 100644 --- a/config/locales/simple_form.gd.yml +++ b/config/locales/simple_form.gd.yml @@ -225,6 +225,7 @@ gd: email: Seòladh puist-d expires_in: Falbhaidh an ùine air às dèidh fields: Raointean a bharrachd + filter_action: Gnìomh na criathraige header: Dealbh a’ bhanna-chinn honeypot: "%{label} (na lìon seo)" inbox_url: URL bogsa a-steach an ath-sheachadain @@ -241,6 +242,7 @@ gd: setting_always_send_emails: Cuir brathan puist-d an-còmhnaidh setting_auto_play_gif: Cluich GIFs beòthaichte gu fèin-obrachail setting_boost_modal: Smachd air faicsinneachd nam brosnachaidhean + setting_color_scheme: Sgeama dhathan setting_contrast: Iomsgaradh setting_default_language: Cànan postaidh setting_default_privacy: Faicsinneachd nam post @@ -355,6 +357,7 @@ gd: hint: Barrachd fiosrachaidh text: Riaghailt settings: + email_subscriptions: Cuir clàraidhean puist-d an comas indexable: Gabh a-staigh duilleag na pròifil sna h-einnseanan-luirg show_application: Seall dè an aplacaid a chuir thu post leatha tag: @@ -388,6 +391,7 @@ gd: name: Ainm permissions_as_keys: Ceadan position: Prìomhachas + require_2fa: Iarr dearbhadh dà-cheumnach username_block: allow_with_approval: Ceadaich clàradh le aontachadh comparison: Dòigh a’ choimheis diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 1c83415babb..db912b94c78 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -480,7 +480,7 @@ vi: export_domain_allows: new: title: Nhập tên miền cho phép - no_file: Không có tập tin nào được chọn + no_file: Không có tệp nào được chọn export_domain_blocks: import: description_html: Bạn sắp nhập danh sách các tên miền chặn. Vui lòng xem lại danh sách này thật cẩn thận, đặc biệt nếu bạn không phải là tác giả của danh sách này. @@ -491,7 +491,7 @@ vi: invalid_domain_block: 'Một hoặc nhiều tên miền đã bị bỏ qua do (các) lỗi sau: %{error}' new: title: Nhập máy chủ chặn - no_file: Không có tập tin nào được chọn + no_file: Không có tệp nào được chọn fasp: debug: callbacks: @@ -886,8 +886,8 @@ vi: federation_authentication: Thực thi xác thực liên hợp title: Cài đặt máy chủ site_uploads: - delete: Xóa tập tin đã tải lên - destroyed_msg: Đã xóa tập tin tải lên thành công! + delete: Xóa tệp đã tải lên + destroyed_msg: Đã xóa tệp tải lên thành công! software_updates: critical_update: Quan trọng — vui lòng cập nhật sớm description: Bạn nên cập nhật Mastodon phiên bản mới nhất để được hưởng lợi từ các bản sửa lỗi và thêm tính năng mới. Nhất là để tránh các vấn đề bảo mật. Vì những lý do này, Mastodon sẽ kiểm tra các bản cập nhật 30 phút một lần và sẽ thông báo cho bạn theo tùy chọn thông báo qua email của bạn. @@ -1468,7 +1468,7 @@ vi: domain_blocks: Máy chủ đã chặn lists: Danh sách mutes: Tài khoản đã phớt lờ - storage: Tập tin + storage: Tệp featured_tags: add_new: Thêm mới errors: @@ -1537,9 +1537,9 @@ vi: errors: empty: File CSV trống incompatible_type: Không tương thích với loại nhập đã chọn - invalid_csv_file: 'Tập tin CSV không hợp lệ. Lỗi: %{error}' + invalid_csv_file: 'Tệp CSV không hợp lệ. Lỗi: %{error}' over_rows_processing_limit: chứa nhiều hơn %{count} hàng - too_large: Tập tin quá lớn + too_large: Tệp quá lớn failures: Thất bại imported: Đã nhập mismatched_types_warning: Có vẻ như bạn đã chọn sai loại cho lần nhập này, vui lòng kiểm tra lại. @@ -1650,7 +1650,7 @@ vi: validations: images_and_video: Không thể đính kèm video vào tút đã chứa hình ảnh not_found: Không tìm thấy %{ids} hoặc nó đã bị đính kèm với tút khác - not_ready: Tập tin này vẫn chưa xử lý xong. Hãy thử lại sau! + not_ready: Tệp này vẫn chưa xử lý xong. Hãy thử lại sau! too_many: Không thể đính kèm hơn 4 tệp migrations: acct: Chuyển sang From 14544dc4dd054f807be5dd1b797d04e09f866e56 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 1 Apr 2026 10:32:47 +0200 Subject: [PATCH 03/20] Fix subject of email subscription notification e-mail being difficult to localize (#38507) --- app/mailers/email_subscription_mailer.rb | 2 +- app/views/email_subscription_mailer/notification.text.erb | 2 +- config/locales/en.yml | 8 ++++---- spec/mailers/email_subscription_mailer_spec.rb | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/mailers/email_subscription_mailer.rb b/app/mailers/email_subscription_mailer.rb index 35bd6da2f99..318aec10d89 100644 --- a/app/mailers/email_subscription_mailer.rb +++ b/app/mailers/email_subscription_mailer.rb @@ -30,7 +30,7 @@ class EmailSubscriptionMailer < ApplicationMailer @statuses = statuses I18n.with_locale(locale) do - mail subject: default_i18n_subject(count: @statuses.size, name: @subscription.account.display_name, excerpt: @statuses.first.text.truncate(17)) + mail subject: I18n.t(@statuses.size == 1 ? 'singular' : 'plural', scope: 'email_subscription_mailer.notification.subject', name: @subscription.account.display_name, excerpt: @statuses.first.text.truncate(17)) end end diff --git a/app/views/email_subscription_mailer/notification.text.erb b/app/views/email_subscription_mailer/notification.text.erb index 7da5261b644..9e657b93f92 100644 --- a/app/views/email_subscription_mailer/notification.text.erb +++ b/app/views/email_subscription_mailer/notification.text.erb @@ -1,4 +1,4 @@ -<%= t '.title', count: @statuses.size, name: display_name(@subscription.account), excerpt: truncate(@statuses.first.text, length: 17) %> +<%= t @statuses.size == 1 ? 'singular' : 'plural', scope: 'email_subscription_mailer.notification.title', name: display_name(@subscription.account), excerpt: truncate(@statuses.first.text, length: 17) %> === diff --git a/config/locales/en.yml b/config/locales/en.yml index 76b25667dfb..883bf18e7a4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1440,11 +1440,11 @@ en: one: Interact with this post and discover more like it. other: Interact with these posts and discover more. subject: - one: 'New post: "%{excerpt}"' - other: New posts from %{name} + plural: New posts from %{name} + singular: 'New post: "%{excerpt}"' title: - one: 'New post: "%{excerpt}"' - other: New posts from %{name} + plural: New posts from %{name} + singular: 'New post: "%{excerpt}"' email_subscriptions: active: Active confirmations: diff --git a/spec/mailers/email_subscription_mailer_spec.rb b/spec/mailers/email_subscription_mailer_spec.rb index 0d8ec6e66b3..4782291145b 100644 --- a/spec/mailers/email_subscription_mailer_spec.rb +++ b/spec/mailers/email_subscription_mailer_spec.rb @@ -30,7 +30,7 @@ RSpec.describe EmailSubscriptionMailer do .to send_email( to: email_subscription.email, from: 'notifications@localhost', - subject: I18n.t('email_subscription_mailer.notification.subject', count: statuses.size, name: email_subscription.account.display_name, excerpt: statuses.first.text.truncate(17)) + subject: I18n.t('email_subscription_mailer.notification.subject.singular', name: email_subscription.account.display_name, excerpt: statuses.first.text.truncate(17)) ) end end @@ -43,7 +43,7 @@ RSpec.describe EmailSubscriptionMailer do .to send_email( to: email_subscription.email, from: 'notifications@localhost', - subject: I18n.t('email_subscription_mailer.notification.subject', count: statuses.size, name: email_subscription.account.display_name, excerpt: ActionController::Base.helpers.truncate(statuses.first.text, length: 17)) + subject: I18n.t('email_subscription_mailer.notification.subject.plural', name: email_subscription.account.display_name, excerpt: ActionController::Base.helpers.truncate(statuses.first.text, length: 17)) ) end end From 62479b7b0af6d56ca58a241e26614ab0c7436891 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Wed, 1 Apr 2026 10:55:41 +0200 Subject: [PATCH 04/20] Remove `collections_federation` feature flag (#38508) --- app/lib/activitypub/activity/accept.rb | 2 +- app/lib/activitypub/activity/add.rb | 4 ++-- app/lib/activitypub/activity/delete.rb | 2 +- .../activitypub/activity/feature_request.rb | 2 +- app/lib/activitypub/activity/update.rb | 2 +- app/models/account.rb | 1 - .../activitypub/process_account_service.rb | 2 +- .../add_account_to_collection_service.rb | 6 ++--- app/services/create_collection_service.rb | 6 ++--- .../delete_collection_item_service.rb | 2 +- app/services/delete_collection_service.rb | 2 +- .../revoke_collection_item_service.rb | 2 +- app/services/update_collection_service.rb | 2 +- spec/lib/activitypub/activity/accept_spec.rb | 2 +- spec/lib/activitypub/activity/add_spec.rb | 4 ++-- spec/lib/activitypub/activity/delete_spec.rb | 2 +- .../activity/feature_request_spec.rb | 2 +- spec/lib/activitypub/activity/update_spec.rb | 2 +- spec/models/account_spec.rb | 22 +++++++------------ .../process_account_service_spec.rb | 2 +- .../add_account_to_collection_service_spec.rb | 4 ++-- .../create_collection_service_spec.rb | 4 ++-- .../delete_collection_item_service_spec.rb | 4 ++-- .../delete_collection_service_spec.rb | 2 +- .../revoke_collection_item_service_spec.rb | 2 +- .../update_collection_service_spec.rb | 4 ++-- 26 files changed, 40 insertions(+), 51 deletions(-) diff --git a/app/lib/activitypub/activity/accept.rb b/app/lib/activitypub/activity/accept.rb index 91610d68cfe..4dc6977b8ef 100644 --- a/app/lib/activitypub/activity/accept.rb +++ b/app/lib/activitypub/activity/accept.rb @@ -5,7 +5,7 @@ class ActivityPub::Activity::Accept < ActivityPub::Activity return accept_follow_for_relay if relay_follow? return accept_follow!(follow_request_from_object) unless follow_request_from_object.nil? return accept_quote!(quote_request_from_object) unless quote_request_from_object.nil? - return accept_feature_request! if Mastodon::Feature.collections_federation_enabled? && feature_request_from_object.present? + return accept_feature_request! if Mastodon::Feature.collections_enabled? && feature_request_from_object.present? case @object['type'] when 'Follow' diff --git a/app/lib/activitypub/activity/add.rb b/app/lib/activitypub/activity/add.rb index cfaf29a3be9..0d22910a9a5 100644 --- a/app/lib/activitypub/activity/add.rb +++ b/app/lib/activitypub/activity/add.rb @@ -13,12 +13,12 @@ class ActivityPub::Activity::Add < ActivityPub::Activity add_featured end when @account.collections_url - return unless Mastodon::Feature.collections_federation_enabled? + return unless Mastodon::Feature.collections_enabled? add_collection else @collection = @account.collections.find_by(uri: value_or_id(@json['target'])) - add_collection_item if @collection && Mastodon::Feature.collections_federation_enabled? + add_collection_item if @collection && Mastodon::Feature.collections_enabled? end end diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb index 62c298a6389..1239462a11f 100644 --- a/app/lib/activitypub/activity/delete.rb +++ b/app/lib/activitypub/activity/delete.rb @@ -3,7 +3,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity def perform return delete_person if @account.uri == object_uri - return delete_feature_authorization! unless !Mastodon::Feature.collections_federation_enabled? || feature_authorization_from_object.nil? + return delete_feature_authorization! unless !Mastodon::Feature.collections_enabled? || feature_authorization_from_object.nil? delete_object end diff --git a/app/lib/activitypub/activity/feature_request.rb b/app/lib/activitypub/activity/feature_request.rb index 67355f6bdaa..5e319734a47 100644 --- a/app/lib/activitypub/activity/feature_request.rb +++ b/app/lib/activitypub/activity/feature_request.rb @@ -4,7 +4,7 @@ class ActivityPub::Activity::FeatureRequest < ActivityPub::Activity include Payloadable def perform - return unless Mastodon::Feature.collections_federation_enabled? + return unless Mastodon::Feature.collections_enabled? return if non_matching_uri_hosts?(@account.uri, @json['id']) @collection = find_or_fetch_collection diff --git a/app/lib/activitypub/activity/update.rb b/app/lib/activitypub/activity/update.rb index b40e8f381b7..8eb2427a84f 100644 --- a/app/lib/activitypub/activity/update.rb +++ b/app/lib/activitypub/activity/update.rb @@ -13,7 +13,7 @@ class ActivityPub::Activity::Update < ActivityPub::Activity update_account elsif supported_object_type? || converted_object_type? update_status - elsif equals_or_includes_any?(@object['type'], ['FeaturedCollection']) && Mastodon::Feature.collections_federation_enabled? + elsif equals_or_includes_any?(@object['type'], ['FeaturedCollection']) && Mastodon::Feature.collections_enabled? update_collection end end diff --git a/app/models/account.rb b/app/models/account.rb index 8c44db813ab..141a9fb7e6d 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -474,7 +474,6 @@ class Account < ApplicationRecord def featureable_by?(other_account) return discoverable? if local? - return false unless Mastodon::Feature.collections_federation_enabled? feature_policy_for_account(other_account).in?(%i(automatic manual)) end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 4a5597f2fe8..65b1d03897e 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -65,7 +65,7 @@ class ActivityPub::ProcessAccountService < BaseService unless @options[:only_key] || @account.suspended? check_featured_collection! if @json['featured'].present? check_featured_tags_collection! if @json['featuredTags'].present? - check_featured_collections_collection! if @json['featuredCollections'].present? && Mastodon::Feature.collections_federation_enabled? + check_featured_collections_collection! if @json['featuredCollections'].present? && Mastodon::Feature.collections_enabled? check_links! if @account.fields.any?(&:requires_verification?) end diff --git a/app/services/add_account_to_collection_service.rb b/app/services/add_account_to_collection_service.rb index c5a4697e797..0cca1a31ff5 100644 --- a/app/services/add_account_to_collection_service.rb +++ b/app/services/add_account_to_collection_service.rb @@ -11,10 +11,8 @@ class AddAccountToCollectionService @collection_item = create_collection_item - if Mastodon::Feature.collections_federation_enabled? - distribute_add_activity if @account.local? - distribute_feature_request_activity if @account.remote? - end + distribute_add_activity if @account.local? + distribute_feature_request_activity if @account.remote? @collection_item end diff --git a/app/services/create_collection_service.rb b/app/services/create_collection_service.rb index 979794224ac..e38bddebbae 100644 --- a/app/services/create_collection_service.rb +++ b/app/services/create_collection_service.rb @@ -9,10 +9,8 @@ class CreateCollectionService @collection.save! - if Mastodon::Feature.collections_federation_enabled? - distribute_add_activity - distribute_feature_request_activities - end + distribute_add_activity + distribute_feature_request_activities @collection end diff --git a/app/services/delete_collection_item_service.rb b/app/services/delete_collection_item_service.rb index 168eb9ba0a9..3f91ea0c3b1 100644 --- a/app/services/delete_collection_item_service.rb +++ b/app/services/delete_collection_item_service.rb @@ -7,7 +7,7 @@ class DeleteCollectionItemService if collection_item.local? revoke ? @collection_item.revoke! : @collection_item.destroy! - distribute_remove_activity if Mastodon::Feature.collections_federation_enabled? + distribute_remove_activity else collection_item.destroy! end diff --git a/app/services/delete_collection_service.rb b/app/services/delete_collection_service.rb index 78b9261fd1d..c2cc6ebd6c3 100644 --- a/app/services/delete_collection_service.rb +++ b/app/services/delete_collection_service.rb @@ -5,7 +5,7 @@ class DeleteCollectionService @collection = collection @collection.destroy! - distribute_remove_activity if Mastodon::Feature.collections_federation_enabled? + distribute_remove_activity end private diff --git a/app/services/revoke_collection_item_service.rb b/app/services/revoke_collection_item_service.rb index 9b5c53f70c2..0b3c2c709e1 100644 --- a/app/services/revoke_collection_item_service.rb +++ b/app/services/revoke_collection_item_service.rb @@ -10,7 +10,7 @@ class RevokeCollectionItemService < BaseService @collection_item.revoke! - distribute_stamp_deletion! if Mastodon::Feature.collections_federation_enabled? && @collection_item.remote? + distribute_stamp_deletion! if @collection_item.remote? end private diff --git a/app/services/update_collection_service.rb b/app/services/update_collection_service.rb index 9dffac9e2bb..f097a8cfb8f 100644 --- a/app/services/update_collection_service.rb +++ b/app/services/update_collection_service.rb @@ -7,7 +7,7 @@ class UpdateCollectionService @collection = collection @collection.update!(params) - distribute_update_activity if Mastodon::Feature.collections_federation_enabled? + distribute_update_activity end private diff --git a/spec/lib/activitypub/activity/accept_spec.rb b/spec/lib/activitypub/activity/accept_spec.rb index f7c1e1d6175..732f01cc6db 100644 --- a/spec/lib/activitypub/activity/accept_spec.rb +++ b/spec/lib/activitypub/activity/accept_spec.rb @@ -172,7 +172,7 @@ RSpec.describe ActivityPub::Activity::Accept do end end - context 'with a FeatureRequest', feature: :collections_federation do + context 'with a FeatureRequest', feature: :collections do let(:collection) { Fabricate(:collection, account: recipient) } let(:collection_item) { Fabricate(:collection_item, collection:, account: sender, state: :pending) } let(:object) { collection_item.activity_uri } diff --git a/spec/lib/activitypub/activity/add_spec.rb b/spec/lib/activitypub/activity/add_spec.rb index d0bdfbe2185..b444f38a3d0 100644 --- a/spec/lib/activitypub/activity/add_spec.rb +++ b/spec/lib/activitypub/activity/add_spec.rb @@ -80,7 +80,7 @@ RSpec.describe ActivityPub::Activity::Add do end end - context 'when the target is the `featuredCollections` collection', feature: :collections_federation do + context 'when the target is the `featuredCollections` collection', feature: :collections do subject { described_class.new(activity_json, account) } let(:account) { Fabricate(:remote_account, collections_url: 'https://example.com/actor/1/featured_collections') } @@ -122,7 +122,7 @@ RSpec.describe ActivityPub::Activity::Add do end end - context 'when the target is a collection', feature: :collections_federation do + context 'when the target is a collection', feature: :collections do subject { described_class.new(activity_json, collection.account) } let(:collection) { Fabricate(:remote_collection) } diff --git a/spec/lib/activitypub/activity/delete_spec.rb b/spec/lib/activitypub/activity/delete_spec.rb index c2b3a63431d..c6d74b4b5b9 100644 --- a/spec/lib/activitypub/activity/delete_spec.rb +++ b/spec/lib/activitypub/activity/delete_spec.rb @@ -120,7 +120,7 @@ RSpec.describe ActivityPub::Activity::Delete do end end - context 'with a FeatureAuthorization', feature: :collections_federation do + context 'with a FeatureAuthorization', feature: :collections do let(:recipient) { Fabricate(:account) } let(:approval_uri) { 'https://example.com/authorizations/1' } let(:collection) { Fabricate(:collection, account: recipient) } diff --git a/spec/lib/activitypub/activity/feature_request_spec.rb b/spec/lib/activitypub/activity/feature_request_spec.rb index cd199a806a4..a7fc14e3259 100644 --- a/spec/lib/activitypub/activity/feature_request_spec.rb +++ b/spec/lib/activitypub/activity/feature_request_spec.rb @@ -20,7 +20,7 @@ RSpec.describe ActivityPub::Activity::FeatureRequest do } end - describe '#perform', feature: :collections_federation do + describe '#perform', feature: :collections do subject { described_class.new(json, sender) } context 'when recipient is discoverable' do diff --git a/spec/lib/activitypub/activity/update_spec.rb b/spec/lib/activitypub/activity/update_spec.rb index ba69c4afc26..78678bde817 100644 --- a/spec/lib/activitypub/activity/update_spec.rb +++ b/spec/lib/activitypub/activity/update_spec.rb @@ -257,7 +257,7 @@ RSpec.describe ActivityPub::Activity::Update do end end - context 'with a `FeaturedCollection` object', feature: :collections_federation do + context 'with a `FeaturedCollection` object', feature: :collections do let(:collection) { Fabricate(:remote_collection, account: sender, name: 'old name', discoverable: false) } let(:featured_collection_json) do { diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 87867601253..a128a9b2f87 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -802,23 +802,17 @@ RSpec.describe Account do let(:discoverable) { true } let(:feature_approval_policy) { (0b10 << 16) | 0 } - it 'returns `false`' do - expect(subject.featureable_by?(local_account)).to be false + context 'when the policy allows it' do + it 'returns `true`' do + expect(subject.featureable_by?(local_account)).to be true + end end - context 'when collections federation is enabled', feature: :collections_federation do - context 'when the policy allows it' do - it 'returns `true`' do - expect(subject.featureable_by?(local_account)).to be true - end - end + context 'when the policy forbids it' do + let(:feature_approval_policy) { 0 } - context 'when the policy forbids it' do - let(:feature_approval_policy) { 0 } - - it 'returns `false`' do - expect(subject.featureable_by?(local_account)).to be false - end + it 'returns `false`' do + expect(subject.featureable_by?(local_account)).to be false end end end diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index 99bcbba9fea..96923a816e9 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -63,7 +63,7 @@ RSpec.describe ActivityPub::ProcessAccountService do end end - context 'with collection URIs', feature: :collections_federation do + context 'with collection URIs', feature: :collections do let(:payload) do { 'id' => 'https://foo.test', diff --git a/spec/services/add_account_to_collection_service_spec.rb b/spec/services/add_account_to_collection_service_spec.rb index c1e3ab45c8f..4f87a2e1b96 100644 --- a/spec/services/add_account_to_collection_service_spec.rb +++ b/spec/services/add_account_to_collection_service_spec.rb @@ -22,14 +22,14 @@ RSpec.describe AddAccountToCollectionService do end context 'when the account is local' do - it 'federates an `Add` activity', feature: :collections_federation do + it 'federates an `Add` activity' do subject.call(collection, account) expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job end end - context 'when the account is remote', feature: :collections_federation do + context 'when the account is remote' do let(:account) { Fabricate(:remote_account, feature_approval_policy: (0b10 << 16)) } it 'marks the item as `pending` and federates a `FeatureRequest` activity' do diff --git a/spec/services/create_collection_service_spec.rb b/spec/services/create_collection_service_spec.rb index 976782679f8..ded9d4d567f 100644 --- a/spec/services/create_collection_service_spec.rb +++ b/spec/services/create_collection_service_spec.rb @@ -29,7 +29,7 @@ RSpec.describe CreateCollectionService do expect(collection).to be_local end - it 'federates an `Add` activity', feature: :collections_federation do + it 'federates an `Add` activity' do subject.call(base_params, author) expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job @@ -65,7 +65,7 @@ RSpec.describe CreateCollectionService do context 'when some accounts are remote' do let(:accounts) { Fabricate.times(2, :remote_account, feature_approval_policy: (0b10 << 16)) } - it 'marks the new items as `pending` and federates `FeatureRequest` activities', feature: :collections_federation do + it 'marks the new items as `pending` and federates `FeatureRequest` activities' do subject.call(params, author) new_collection = author.collections.last diff --git a/spec/services/delete_collection_item_service_spec.rb b/spec/services/delete_collection_item_service_spec.rb index d7bfb97f674..f9a235b902e 100644 --- a/spec/services/delete_collection_item_service_spec.rb +++ b/spec/services/delete_collection_item_service_spec.rb @@ -14,7 +14,7 @@ RSpec.describe DeleteCollectionItemService do end context 'when the collection is local' do - it 'federates a `Remove` activity', feature: :collections_federation do + it 'federates a `Remove` activity' do subject.call(collection_item) expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job @@ -33,7 +33,7 @@ RSpec.describe DeleteCollectionItemService do let(:collection) { Fabricate(:remote_collection) } let!(:collection_item) { Fabricate(:collection_item, collection:, state: :accepted) } - it 'destroys the collection withouth federating anything', feature: :collections_federation do + it 'destroys the collection withouth federating anything' do expect { subject.call(collection_item, revoke: true) }.to change(collection.collection_items, :count).by(-1) expect(ActivityPub::AccountRawDistributionWorker).to_not have_enqueued_sidekiq_job diff --git a/spec/services/delete_collection_service_spec.rb b/spec/services/delete_collection_service_spec.rb index bd4ed5fef6f..06ed82b21db 100644 --- a/spec/services/delete_collection_service_spec.rb +++ b/spec/services/delete_collection_service_spec.rb @@ -12,7 +12,7 @@ RSpec.describe DeleteCollectionService do expect { subject.call(collection) }.to change(Collection, :count).by(-1) end - it 'federates a `Remove` activity', feature: :collections_federation do + it 'federates a `Remove` activity' do subject.call(collection) expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job diff --git a/spec/services/revoke_collection_item_service_spec.rb b/spec/services/revoke_collection_item_service_spec.rb index b4cba82056e..18357f53ddf 100644 --- a/spec/services/revoke_collection_item_service_spec.rb +++ b/spec/services/revoke_collection_item_service_spec.rb @@ -12,7 +12,7 @@ RSpec.describe RevokeCollectionItemService do .to change { collection_item.reload.state }.from('accepted').to('revoked') end - context 'when the collection is remote', feature: :collections_federation do + context 'when the collection is remote' do let(:account) { Fabricate(:remote_account, inbox_url: 'https://example.com/actor/1/inbox') } let(:collection) { Fabricate(:remote_collection, account:) } let(:collection_item) { Fabricate(:collection_item, collection:, uri: 'https://example.com') } diff --git a/spec/services/update_collection_service_spec.rb b/spec/services/update_collection_service_spec.rb index b7b9547dbb9..32dd88216cb 100644 --- a/spec/services/update_collection_service_spec.rb +++ b/spec/services/update_collection_service_spec.rb @@ -16,7 +16,7 @@ RSpec.describe UpdateCollectionService do end context 'when something actually changed' do - it 'federates an `Update` activity', feature: :collections_federation do + it 'federates an `Update` activity' do subject.call(collection, { name: 'updated' }) expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job @@ -24,7 +24,7 @@ RSpec.describe UpdateCollectionService do end context 'when nothing changed' do - it 'does not federate an activity', feature: :collections_federation do + it 'does not federate an activity' do subject.call(collection, { name: collection.name }) expect(ActivityPub::AccountRawDistributionWorker).to_not have_enqueued_sidekiq_job From 13e21fb1f22c9d30227a18a3273509b942086ed4 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 1 Apr 2026 05:08:15 -0400 Subject: [PATCH 05/20] Improve resilience of `tootctl maintenance fix-duplicates` command (#38501) Co-authored-by: Claire --- lib/mastodon/cli/maintenance.rb | 79 +++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/lib/mastodon/cli/maintenance.rb b/lib/mastodon/cli/maintenance.rb index e5db9d6ed23..c575c45c1f9 100644 --- a/lib/mastodon/cli/maintenance.rb +++ b/lib/mastodon/cli/maintenance.rb @@ -282,8 +282,9 @@ module Mastodon::CLI deduplicate_remote_accounts!(accounts) end end - + ensure say 'Restoring index_accounts_on_username_and_domain_lower…' + if migrator_version < 2020_06_20_164023 database_connection.add_index :accounts, 'lower (username), lower(domain)', name: 'index_accounts_on_username_and_domain_lower', unique: true else @@ -309,8 +310,9 @@ module Mastodon::CLI deduplicate_users_process_confirmation_token deduplicate_users_process_remember_token deduplicate_users_process_password_token - + ensure say 'Restoring users indexes…' + database_connection.add_index :users, ['confirmation_token'], name: 'index_users_on_confirmation_token', unique: true database_connection.add_index :users, ['email'], name: 'index_users_on_email', unique: true database_connection.add_index :users, ['remember_token'], name: 'index_users_on_remember_token', unique: true if migrator_version < 2022_01_18_183010 @@ -380,8 +382,9 @@ module Mastodon::CLI duplicate_record_ids(:account_domain_blocks, 'account_id, domain').each do |row| AccountDomainBlock.where(id: row['ids'].split(',').drop(1)).delete_all end - + ensure say 'Restoring account domain blocks indexes…' + database_connection.add_index :account_domain_blocks, %w(account_id domain), name: 'index_account_domain_blocks_on_account_id_and_domain', unique: true end @@ -394,9 +397,11 @@ module Mastodon::CLI duplicate_record_ids(:account_identity_proofs, 'account_id, provider, provider_username').each do |row| AccountIdentityProof.where(id: row['ids'].split(',')).order(id: :desc).to_a.drop(1).each(&:destroy) end - - say 'Restoring account identity proofs indexes…' - database_connection.add_index :account_identity_proofs, %w(account_id provider provider_username), name: 'index_account_proofs_on_account_and_provider_and_username', unique: true + ensure + if db_table_exists?(:account_identity_proofs) + say 'Restoring account identity proofs indexes…' + database_connection.add_index :account_identity_proofs, %w(account_id provider provider_username), name: 'index_account_proofs_on_account_and_provider_and_username', unique: true + end end def deduplicate_announcement_reactions! @@ -408,9 +413,11 @@ module Mastodon::CLI duplicate_record_ids(:announcement_reactions, 'account_id, announcement_id, name').each do |row| AnnouncementReaction.where(id: row['ids'].split(',')).order(id: :desc).to_a.drop(1).each(&:destroy) end - - say 'Restoring announcement_reactions indexes…' - database_connection.add_index :announcement_reactions, %w(account_id announcement_id name), name: 'index_announcement_reactions_on_account_id_and_announcement_id', unique: true + ensure + if db_table_exists?(:announcement_reactions) + say 'Restoring announcement_reactions indexes…' + database_connection.add_index :announcement_reactions, %w(account_id announcement_id name), name: 'index_announcement_reactions_on_account_id_and_announcement_id', unique: true + end end def deduplicate_conversations! @@ -427,8 +434,9 @@ module Mastodon::CLI other.destroy end end - + ensure say 'Restoring conversations indexes…' + if migrator_version < 2022_03_07_083603 database_connection.add_index :conversations, ['uri'], name: 'index_conversations_on_uri', unique: true else @@ -450,8 +458,9 @@ module Mastodon::CLI other.destroy end end - + ensure say 'Restoring custom_emojis indexes…' + database_connection.add_index :custom_emojis, %w(shortcode domain), name: 'index_custom_emojis_on_shortcode_and_domain', unique: true end @@ -469,8 +478,9 @@ module Mastodon::CLI other.destroy end end - + ensure say 'Restoring custom_emoji_categories indexes…' + database_connection.add_index :custom_emoji_categories, ['name'], name: 'index_custom_emoji_categories_on_name', unique: true end @@ -481,8 +491,9 @@ module Mastodon::CLI duplicate_record_ids(:domain_allows, 'domain').each do |row| DomainAllow.where(id: row['ids'].split(',')).order(id: :desc).to_a.drop(1).each(&:destroy) end - + ensure say 'Restoring domain_allows indexes…' + database_connection.add_index :domain_allows, ['domain'], name: 'index_domain_allows_on_domain', unique: true end @@ -505,8 +516,9 @@ module Mastodon::CLI domain_blocks.each(&:destroy) end - + ensure say 'Restoring domain_blocks indexes…' + database_connection.add_index :domain_blocks, ['domain'], name: 'index_domain_blocks_on_domain', unique: true end @@ -519,9 +531,11 @@ module Mastodon::CLI duplicate_record_ids(:unavailable_domains, 'domain').each do |row| UnavailableDomain.where(id: row['ids'].split(',')).order(id: :desc).to_a.drop(1).each(&:destroy) end - - say 'Restoring unavailable_domains indexes…' - database_connection.add_index :unavailable_domains, ['domain'], name: 'index_unavailable_domains_on_domain', unique: true + ensure + if db_table_exists?(:unavailable_domains) + say 'Restoring unavailable_domains indexes…' + database_connection.add_index :unavailable_domains, ['domain'], name: 'index_unavailable_domains_on_domain', unique: true + end end def deduplicate_email_domain_blocks! @@ -532,8 +546,9 @@ module Mastodon::CLI domain_blocks = EmailDomainBlock.where(id: row['ids'].split(',')).order(EmailDomainBlock.arel_table[:parent_id].asc.nulls_first).to_a domain_blocks.drop(1).each(&:destroy) end - + ensure say 'Restoring email_domain_blocks indexes…' + database_connection.add_index :email_domain_blocks, ['domain'], name: 'index_email_domain_blocks_on_domain', unique: true end @@ -544,8 +559,9 @@ module Mastodon::CLI duplicate_record_ids_without_nulls(:media_attachments, 'shortcode').each do |row| MediaAttachment.where(id: row['ids'].split(',').drop(1)).update_all(shortcode: nil) end - + ensure say 'Restoring media_attachments indexes…' + if migrator_version < 2022_03_10_060626 database_connection.add_index :media_attachments, ['shortcode'], name: 'index_media_attachments_on_shortcode', unique: true else @@ -560,8 +576,9 @@ module Mastodon::CLI duplicate_record_ids(:preview_cards, 'url').each do |row| PreviewCard.where(id: row['ids'].split(',')).order(id: :desc).to_a.drop(1).each(&:destroy) end - + ensure say 'Restoring preview_cards indexes…' + database_connection.add_index :preview_cards, ['url'], name: 'index_preview_cards_on_url', unique: true end @@ -577,8 +594,9 @@ module Mastodon::CLI status.destroy end end - + ensure say 'Restoring statuses indexes…' + if migrator_version < 2022_03_10_060706 database_connection.add_index :statuses, ['uri'], name: 'index_statuses_on_uri', unique: true else @@ -599,8 +617,9 @@ module Mastodon::CLI tag.destroy end end - + ensure say 'Restoring tags indexes…' + if migrator_version < 2021_04_21_121431 database_connection.add_index :tags, 'lower((name)::text)', name: 'index_tags_on_name_lower', unique: true else @@ -617,9 +636,11 @@ module Mastodon::CLI duplicate_record_ids(:webauthn_credentials, 'external_id').each do |row| WebauthnCredential.where(id: row['ids'].split(',')).order(id: :desc).to_a.drop(1).each(&:destroy) end - - say 'Restoring webauthn_credentials indexes…' - database_connection.add_index :webauthn_credentials, ['external_id'], name: 'index_webauthn_credentials_on_external_id', unique: true + ensure + if db_table_exists?(:webauthn_credentials) + say 'Restoring webauthn_credentials indexes…' + database_connection.add_index :webauthn_credentials, ['external_id'], name: 'index_webauthn_credentials_on_external_id', unique: true + end end def deduplicate_webhooks! @@ -631,9 +652,11 @@ module Mastodon::CLI duplicate_record_ids(:webhooks, 'url').each do |row| Webhook.where(id: row['ids'].split(',')).order(id: :desc).drop(1).each(&:destroy) end - - say 'Restoring webhooks indexes…' - database_connection.add_index :webhooks, ['url'], name: 'index_webhooks_on_url', unique: true + ensure + if db_table_exists?(:webhooks) + say 'Restoring webhooks indexes…' + database_connection.add_index :webhooks, ['url'], name: 'index_webhooks_on_url', unique: true + end end def deduplicate_software_updates! From eda0f62f89fd06450a70da4df19f2cd8d22ff98e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:16:06 +0200 Subject: [PATCH 06/20] New Crowdin Translations (automated) (#38505) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/gd.json | 32 ++++++++++++++++++++++++- config/locales/activerecord.ja.yml | 6 +++++ config/locales/devise.ja.yml | 1 + config/locales/doorkeeper.ja.yml | 4 ++++ config/locales/gd.yml | 15 ++++++++++++ config/locales/simple_form.gd.yml | 2 ++ 6 files changed, 59 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 9fc37b55e06..597476735f9 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -153,6 +153,7 @@ "account_edit.custom_fields.edit_label": "Deasaich an raon", "account_edit.custom_fields.placeholder": "Cuir ris do riochdairean, ceanglaichean dhan taobh a-muigh no rud sam bith eile a bu mhiann leat co-roinneadh.", "account_edit.custom_fields.reorder_button": "Atharraich òrdugh nan raointean", + "account_edit.custom_fields.tip_content": "Cuir ri teistealachd do chunntais Mhastodon gun duilgheadas le dearbhadh cheanglaichean gu duilleag-lìn sam bith a tha leatsa.", "account_edit.custom_fields.tip_title": "Gliocas: Cuir ceangalaichean dearbhte ris", "account_edit.custom_fields.title": "Raointean gnàthaichte", "account_edit.custom_fields.verified_hint": "Ciamar a chuireas mi ceangal dearbhte ris?", @@ -161,6 +162,7 @@ "account_edit.display_name.placeholder": "’S e mar a nochdas d’ ainm air a’ phròifil agad agus air loidhnichean-ama a tha san ainm-taisbeanaidh agad.", "account_edit.display_name.title": "Ainm-taisbeanaidh", "account_edit.featured_hashtags.edit_label": "Cuir tagaichean hais ris", + "account_edit.featured_hashtags.placeholder": "Cuidich càch ach an aithnich iad na cuspairean as fheàrr leat ’s gum faigh iad grèim orra gu sgiobalta.", "account_edit.featured_hashtags.title": "Tagaichean hais brosnaichte", "account_edit.field_actions.delete": "Sguab às an raon", "account_edit.field_actions.edit": "Deasaich an raon", @@ -171,6 +173,8 @@ "account_edit.field_edit_modal.discard_confirm": "Tilg air falbh", "account_edit.field_edit_modal.discard_message": "Tha atharraichean gun sàbhaladh agad. A bheil thu cinnteach gu bheil airson an tilgeil air falbh?", "account_edit.field_edit_modal.edit_title": "Deasaich an raoin gnàthaichte", + "account_edit.field_edit_modal.length_warning": "Chaidh thu thar crìoch nan caractaran a mholamaid. Dh’fhaoidte nach fhaicear an raon agad gu lèir air mobile.", + "account_edit.field_edit_modal.link_emoji_warning": "Mholamaid nach cleachd thu emojis gnàthaichte le URLaichean. Thèid raointean gnàthaichte sa bheil an dà chuid còmhla a shealltainn ’nan teacsa a-mhàin seach ’nan ceangal ach nach cuireamaid an luchd-cleachdaidh tro chèile.", "account_edit.field_edit_modal.name_hint": "Can “Làrach-lìn phearsanta”", "account_edit.field_edit_modal.name_label": "Leubail", "account_edit.field_edit_modal.url_warning": "Airson ceangal a chur ris, gabh a-staigh {protocol} aig a thoiseach.", @@ -178,14 +182,17 @@ "account_edit.field_edit_modal.value_label": "Luach", "account_edit.field_reorder_modal.drag_cancel": "Chaidh sgur dhen t-slaodadh. Chaidh an raon “{item}” a leigeil às.", "account_edit.field_reorder_modal.drag_end": "Chaidh an raon “{item}” a leigeil às.", + "account_edit.field_reorder_modal.drag_instructions": "Airson òrdugh nan raointean gnàthaichte atharrachadh, brùth air space no enter. Fhad ’s a bhios tu ri slaodadh, cleachd na h-iuchraichean saighde a ghluasad an raoin suas no sìos. Brùth air space no enter a-rithist a leigeil às an raoin air an ionad ùr aige no brùth air escape airson sgur dheth.", "account_edit.field_reorder_modal.drag_move": "Chaidh an raon “{item}” a ghluasad.", "account_edit.field_reorder_modal.drag_over": "Chaidh an raon “{item}” a ghluasad thar “{over}”.", "account_edit.field_reorder_modal.drag_start": "Chaidh an raon “{item}” a thogail.", "account_edit.field_reorder_modal.handle_label": "Slaod an raon “{item}”", "account_edit.field_reorder_modal.title": "Cuir òrdugh ùr air na raointean", "account_edit.image_alt_modal.add_title": "Cuir roghainn teacsa ris", + "account_edit.image_alt_modal.details_content": "MHOLAMAID:
  • Gun doir thu dealbh ort
  • Gun cleachd thu cainnt treas pearsa (can “Ailig” seach “mise”)
  • Nach cleachd thu ach beagan fhaclan
SEACHAINN:
  • Tòiseachadh le “Dealbh-camara de” – bhiodh sin anabarrach do leughadairean-sgrìn
BALL-EISIMPLEIR:
  • “Ailig a’ caitheamh lèine ghorm agus speuclairean”
", "account_edit.image_alt_modal.details_title": "Gliocasan: Roghainn teacsa air dealbhan pròifile", "account_edit.image_alt_modal.edit_title": "Deasaich an roghainn teacsa", + "account_edit.image_alt_modal.text_hint": "Cuidichidh roghainn teacsa ach an tuigeadh ann fheadhainn a chleachdas leughadair-sgrìn do shusbaint.", "account_edit.image_alt_modal.text_label": "Roghainn teacsa", "account_edit.image_delete_modal.confirm": "A bheil thu cinnteach gu bheil thu airson an dealbh seo a sguabadh às? Cha ghabh seo a neo-dhèanamh.", "account_edit.image_delete_modal.delete_button": "Sguab às", @@ -200,8 +207,13 @@ "account_edit.name_modal.add_title": "Cuir ris ainm-taisbeanaidh", "account_edit.name_modal.edit_title": "Deasaich an t-ainm-taisbeanaidh", "account_edit.profile_tab.button_label": "Gnàthaich", + "account_edit.profile_tab.hint.description": "Gnàthaichidh na roghainnean seo na chì an luchd-cleachdaidh air {server} sna h-aplacaidean oifigeil ach dh’fhaoidte nach bi iad an sàs dhan luchd-cleachdaidh air frithealaichean eile is aplacaidean treas-phàrtaidh.", + "account_edit.profile_tab.hint.title": "Bidh na chithear fhathast eadar-dhealaichte", + "account_edit.profile_tab.show_featured.description": "’S e taba roghainneil a th’ ann an “’Ga bhrosnachadh” far an urrainn dhut cunntasan eile a nochdadh.", "account_edit.profile_tab.show_featured.title": "Seall an taba “’Ga bhrosnachadh”", + "account_edit.profile_tab.show_media.description": "’S e taba roghainneil a th’ ann am “Meadhanan” a sheallas na postaichean agad ris a bheil dealbh no video.", "account_edit.profile_tab.show_media.title": "Seall an taba “Meadhanan”", + "account_edit.profile_tab.show_media_replies.description": "Nuair a bhios seo an comas, seallaidh an taba “Meadhanan” an dà chuid na postaichean agad agus na freagairtean a rinn thu do phostaichean càich.", "account_edit.profile_tab.show_media_replies.title": "Gabh a-staigh freagairtean air an taba “Meadhanan”", "account_edit.profile_tab.subtitle": "Gnàthaich na tabaichean air a’ phròifil agad is na sheallas iad.", "account_edit.profile_tab.title": "Roghainnean tabaichean na pròifile", @@ -218,6 +230,7 @@ "account_edit.upload_modal.title_add.header": "Cuir dealbh còmhdachaidh ris", "account_edit.upload_modal.title_replace.avatar": "Cuir dealbh ùr an àite dealbh na pròifil", "account_edit.upload_modal.title_replace.header": "Cuir dealbh ùr an àite an deilbh chòmhdachaidh", + "account_edit.verified_modal.details": "Cuir ri teistealachd do chunntais Mhastodon le dearbhadh cheanglaichean gu duilleagan-lìn parsanta. Seo mar a nì thu e:", "account_edit.verified_modal.invisible_link.details": "Cuir an ceangal ris a’ bhann-chinn agad. ’S e rel=\"me\" a tha sa phàirt chudromach a bhacas riochd cuideigin eile air làraichean-lìn le susbaint air a gintinn o chleachdaiche. ’S urrainn dhut fiù taga link a chleachdadh ann am bann-cinn na duilleige seach {tag} ach feumaidh sinn an HTML ruigsinn gun a bhith a’ ruith JavaScript.", "account_edit.verified_modal.invisible_link.summary": "Ciamar a dh’fhalaicheas mi an ceangal?", "account_edit.verified_modal.step1.header": "Dèan lethbhreac dhen chòd HTML gu h-ìosal is cuir e ri bann-cinn na làraich-lìn agad", @@ -226,6 +239,8 @@ "account_edit.verified_modal.title": "Mar a chuireas tu ceangal dearbhte ris", "account_edit_tags.add_tag": "Cuir #{tagName} ris", "account_edit_tags.column_title": "Deasaich na tagaichean", + "account_edit_tags.help_text": "Cuidichidh na tagaichean hais brosnaichte gun lorg an luchd-cleachdaidh a’ phròifil agad ’s ach an dèan iad conaltradh ris. Nochdaidh iad ’nan criathragan air sealladh “Gnìomhachd” duilleag na pròifil agad.", + "account_edit_tags.max_tags_reached": "Ràinig thu na tha ceadaichte dhut de thagaichean hais brosnaichte.", "account_edit_tags.search_placeholder": "Cuir a-steach taga hais…", "account_edit_tags.suggestions": "Molaidhean:", "account_edit_tags.tag_status_count": "{count, plural, one {# phost} two {# phost} few {# postaichean} other {# post}}", @@ -353,17 +368,23 @@ "collections.confirm_account_removal": "A bheil thu cinnteach gu bheil thu airson an cunntas seo a thoirt air falbh on chruinneachadh seo?", "collections.content_warning": "Rabhadh susbainte", "collections.continue": "Lean air adhart", + "collections.create.accounts_subtitle": "Chan urrainn dhut cur ris ach cunntasan a leanas tu ’s a ghabh ri rùrachadh.", + "collections.create.accounts_title": "Cò bhrosnaicheas tu sa chruinneachadh seo?", "collections.create.basic_details_title": "Bun-fhiosrachadh", "collections.create.steps": "Ceum {step}/{total}", + "collections.create_a_collection_hint": "Cruthaich cruinneachadh airson na cunntasan as fheàrr leat a mholadh no a cho-roinneadh le càch.", "collections.create_collection": "Cruthaich cruinneachadh", "collections.delete_collection": "Sguab an cruinneachadh às", "collections.description_length_hint": "Crìoch de 100 caractar", "collections.detail.accept_inclusion": "Taghta", "collections.detail.accounts_heading": "Cunntasan", "collections.detail.author_added_you": "Chuir {author} ris a’ chruinneachadh seo thu", + "collections.detail.curated_by_author": "’Ga thasgadh le {author}", + "collections.detail.curated_by_you": "’Ga thasgadh leatsa ", "collections.detail.loading": "A’ luchdadh a’ chruinneachaidh…", "collections.detail.other_accounts_in_collection": "Daoine eile sa chruinneachadh seo:", "collections.detail.revoke_inclusion": "Thoir air falbh mi", + "collections.detail.sensitive_note": "Tha cunntasan is susbaint sa chruinneachadh seo a dh’fhaodadh a bhith frionasach do chuid.", "collections.detail.share": "Co-roinn an cruinneachadh seo", "collections.edit_details": "Deasaich am fiosrachadh", "collections.error_loading_collections": "Thachair mearachd nuair a dh’fheuch sinn ris a’ chruinneachaidhean agad a luchdadh.", @@ -371,6 +392,7 @@ "collections.last_updated_at": "An tùrachadh mu dheireadh: {date}", "collections.manage_accounts": "Stiùirich na cunntasan", "collections.mark_as_sensitive": "Cuir comharra gu bheil e frionasach", + "collections.mark_as_sensitive_hint": "Falaichidh seo tuairisgeul is cunntasan a’ chruinneachaidh air cùlaibh rabhadh susbainte. Chithear ainm a’ chruinneachaidh fhathast.", "collections.name_length_hint": "Crìoch de 40 caractar", "collections.new_collection": "Cruinneachadh ùr", "collections.no_collections_yet": "Chan eil cruinneachadh agad fhathast.", @@ -381,10 +403,14 @@ "collections.revoke_inclusion.confirmation": "Chaidh do thoirt air falbh o “{collection}”", "collections.revoke_inclusion.error": "Thachair mearachd. Feuch ris a-rithist an ceann greis.", "collections.search_accounts_label": "Lorg cunntasan gus an cur ris…", + "collections.search_accounts_max_reached": "Chuir thu na tha ceadaichte de chunntasan ris", "collections.sensitive": "Frionasach", + "collections.topic_hint": "Cuir taga hais ris a chuidicheas càch le tuigse prìomh-chuspair a’ chruinneachaidh seo.", + "collections.topic_special_chars_hint": "Thèid caractaran sònraichte a thoirt air falbh nuair a thèid a shàbhaladh", "collections.view_collection": "Seall an cruinneachadh ", "collections.view_other_collections_by_user": "Seall cruinneachaidhean eile aig a’ chleachdaiche seo", "collections.visibility_public": "Poblach", + "collections.visibility_public_hint": "Gabhaidh a rùrachadh ann an toraidhean luirg agus air àitichean eile far a nochdas molaidhean.", "collections.visibility_title": "Faicsinneachd", "collections.visibility_unlisted": "Falaichte o liostaichean", "collections.visibility_unlisted_hint": "Chì duine sam bith aig a bheil ceangal e. Thèid fhalach o thoraidhean luirg ’s na molaidhean.", @@ -501,6 +527,7 @@ "confirmations.remove_from_followers.message": "Cha lean {name} thu tuilleadh. A bheil thu cinnteach gu bheil thu airson leantainn air adhart?", "confirmations.remove_from_followers.title": "A bheil thu airson an neach-leantainn a thoirt air falbh?", "confirmations.revoke_collection_inclusion.confirm": "Thoir air falbh mi", + "confirmations.revoke_collection_inclusion.message": "Tha an gnìomh seo buan agus chan urrainn dhan neach-tasgaidh do chur ris a’ chruinneachadh a-rithist an uair sin.", "confirmations.revoke_collection_inclusion.title": "A bheil tu airson do thoirt air falbh on chruinneachadh seo?", "confirmations.revoke_quote.confirm": "Thoir am post air falbh", "confirmations.revoke_quote.message": "Cha ghabh seo a neo-dhèanamh.", @@ -612,6 +639,7 @@ "featured_carousel.current": "Post {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Post prìnichte} two {Postaichean prìnichte} few {Postaichean prìnichte} other {Postaichean prìnichte}}", "featured_carousel.slide": "Post {current, number} à {max, number}", + "featured_tags.more_items": "+{count}", "featured_tags.suggestions": "Sgrìobh thu mu {items} o chionn goirid. A bheil thu airson an cur ris ’nan tagaichean hais brosnaichte?", "featured_tags.suggestions.add": "Cuir ris", "featured_tags.suggestions.added": "Stiùirich na tagaichean hais brosnaichte agad uair sam bith aig Deasaich a’ phròifil > Tagaichean hais brosnaichte.", @@ -657,7 +685,9 @@ "follow_suggestions.view_all": "Seall na h-uile", "follow_suggestions.who_to_follow": "Molaidhean leantainn", "followed_tags": "Tagaichean hais ’gan leantainn", + "followers.hide_other_followers": "Chuir an cleachdaiche seo roimhe nach fhaicear an luchd-leantainn eile aca", "followers.title": "A’ leantainn {name}", + "following.hide_other_following": "Chuir an cleachdaiche seo roimhe nach fhaicear cò eile a leanas iad", "following.title": "’Ga leantainn le {name}", "footer.about": "Mu dhèidhinn", "footer.about_mastodon": "Mu Mhastodon", @@ -989,7 +1019,7 @@ "onboarding.profile.title": "Suidheachadh na pròifile", "onboarding.profile.upload_avatar": "Luchdaich suas dealbh na pròifil", "onboarding.profile.upload_header": "Luchdaich suas bann-cinn na pròifil", - "password_confirmation.exceeds_maxlength": "Tha dearbhadh an fhacail-fhaire nas fhaide na tha ceadaichte do dh’faclan-faire", + "password_confirmation.exceeds_maxlength": "Tha dearbhadh an fhacail-fhaire nas fhaide na tha ceadaichte do dh’fhaclan-faire", "password_confirmation.mismatching": "Chan eil an dearbhadh co-ionnan ris an fhacal-fhaire", "picture_in_picture.restore": "Thoir air ais e", "poll.closed": "Dùinte", diff --git a/config/locales/activerecord.ja.yml b/config/locales/activerecord.ja.yml index 156e537ddbc..6f3387a72c4 100644 --- a/config/locales/activerecord.ja.yml +++ b/config/locales/activerecord.ja.yml @@ -32,6 +32,12 @@ ja: attributes: url: invalid: は無効なURLです + collection: + attributes: + collection_items: + too_many: は多すぎます。 %{count} 以下にしてください + tag: + unusable: は使用できません doorkeeper/application: attributes: website: diff --git a/config/locales/devise.ja.yml b/config/locales/devise.ja.yml index 686299837c9..067886de8ea 100644 --- a/config/locales/devise.ja.yml +++ b/config/locales/devise.ja.yml @@ -7,6 +7,7 @@ ja: send_paranoid_instructions: もしあなたのメールアドレスが登録されていれば、まもなくメールアドレスの確認の方法が記載されたメールが送信されます。 failure: already_authenticated: 既にログイン済みです。 + closed_registrations: アカウントの登録はネットワークポリシーによりブロックされました。これが誤りだと思われる場合は %{email} まで連絡してください。 inactive: あなたのアカウントはまだ有効化されていません。 invalid: "%{authentication_keys}かパスワードが誤っています。" last_attempt: あと1回失敗するとアカウントがロックされます。 diff --git a/config/locales/doorkeeper.ja.yml b/config/locales/doorkeeper.ja.yml index a788b5bf422..08f4112c7e8 100644 --- a/config/locales/doorkeeper.ja.yml +++ b/config/locales/doorkeeper.ja.yml @@ -83,6 +83,10 @@ ja: access_denied: リソースの所有者または認証サーバーが要求を拒否しました。 credential_flow_not_configured: リソース所有者のパスワード Doorkeeper.configure.resource_owner_from_credentials が設定されていないためクレデンシャルフローに失敗しました。 invalid_client: 不明なクライアントであるか、クライアント情報が含まれていない、またはサポートされていない認証方法のため、クライアントの認証に失敗しました。 + invalid_code_challenge_method: + one: code_challenge_methodは %{challenge_methods} でなければなりません。 + other: code_challenge_methodは %{challenge_methods} のいずれかでなければなりません。 + zero: 認証サーバーが受け入れる code_challenge_method 値がないため、PKCE をサポートしません。 invalid_grant: 指定された認証許可は無効であるか、期限切れ、取り消されている、リダイレクトURIの不一致、または別のクライアントに発行されています。 invalid_redirect_uri: 無効なリダイレクトURIが含まれています。 invalid_request: diff --git a/config/locales/gd.yml b/config/locales/gd.yml index 9c363fb1856..f5223b25fde 100644 --- a/config/locales/gd.yml +++ b/config/locales/gd.yml @@ -812,6 +812,7 @@ gd: delete_user_data: Sguabadh às dàta cleachdaiche delete_user_data_description: Leigidh seo le cleachdaichean dàta chleachdaichean eile a sguabadh às gun dàil invite_bypass_approval: Thoir cuireadh do chleachdaichean gun lèirmheas + invite_bypass_approval_description: Leigidh seo leis an fheadhainn a fhuair cuireadh dhan fhrithealaiche leis na cleachdaichean seo dearbhadh na maorsainneachd a leigeil seachad invite_users: Thoir cuireadh do chleachdaichean invite_users_description: Leigidh seo le cleachdaichean cuireadh dhan fhrithealaiche a chur gu daoine eile manage_announcements: Stiùireadh nam brathan-fios @@ -1464,6 +1465,7 @@ gd: other: Eile redesign_body: "’S urrainn dhut a’ phròifil agad a dheasachadh air duilleag na pròifile fhèin." redesign_button: Tadhail air + redesign_title: Tha dòigh ùr air deasachadh na pròifil againn email_subscription_mailer: confirmation: action: Dearbh an seòladh puist-d @@ -1473,11 +1475,20 @@ gd: title: A bheil thu airson naidheachdan %{name} fhaighinn air a’ phost-d? notification: create_account: Cruthaich cunntas Mastodon + footer: + privacy_html: Thèid puist-d a chur o %{domain}, seo frithealaiche le cumhachd Mhastodon. Airson tuigsinn mar a nì am frithealaiche seo pròiseasadh air an dàta phearsanta agad, faic am poileasaidh prìobhaideachd. + reason_for_email_html: Fhuair thu am post-d seo on a chuir thu romhad naidheachdan %{name} fhaighinn air a’ phost-d. Nach eil thu airson na puist-d seo fhaighinn? Cuir crìoch air an fho-sgrìobhadh + interact_with_this_post: + few: Dèan conaltradh leis na puist seo is lorg barrachd coltach riutha. + one: Dèan conaltradh leis a’ phost seo is lorg barrachd coltach ris. + other: Dèan conaltradh leis na puist seo is lorg barrachd coltach riutha. + two: Dèan conaltradh leis na puist seo is lorg barrachd coltach riutha. email_subscriptions: active: Gnìomhach confirmations: show: changed_your_mind: Na chuir thu romhad a chaochladh? + success_html: Gheibh thu puist-d a-nis nuair a dh’fhoillsicheas %{name} postaichean ùra. Cuir %{sender} ris an luchd-aithne agad ach nach dèid na postaichean seo a chur do phasgan an spama agad. title: Tha thu air clàradh unsubscribe: Cuir crìoch air an fho-sgrìobhadh inactive: Neo-ghnìomhach @@ -1895,6 +1906,7 @@ gd: public_timelines: Loidhnichean-ama poblach privacy: email_subscriptions: Cuir postaichean air a’ phost-d + email_subscriptions_hint_html: Cuir foirm clàradh puist-d ris a’ phròifil agad a nochdas do chleachdaichean nach do rinn clàradh a-steach. Nuair a chuireas aoighean an seòladh puist-d aca a-steach is ma ghabhas iad ris, cuiridh Mastodon naidheachdan mu na postaichean poblach agad thuca air a’ phost-d. hint_html: "Gnàthaich an dòigh air an dèid a’ phròifil ’s na postaichean agad a lorg. Tha grunn ghleusan aig Mastodon a chuidicheas ach an ruig thu èisteachd nas fharsainge nuair a bhios iad an comas. Thoir sùil air na roghainnean seo a dhèanamh cinnteach gum freagair iad ri d’ fheumalachdan." privacy: Prìobhaideachd privacy_hint_html: Stiùirich na tha thu airson foillseachadh do chàch. Gheibh daoine lorg air pròifilean inntinneach is deagh aplacaidean a’ brabhsadh cò tha daoine eile a’ leantainn ’s a’ faicinn nan aplacaidean a chleachdas iad airson postadh ach dh’fhaoidte gum b’ fheàrr leat seo a chumail falaichte. @@ -2137,6 +2149,8 @@ gd: past_preamble_html: Dh’atharraich sinn teirmichean na seirbheise againn on turas mu dheireadh a thadhail thu oirnn. Mholamaid gun dèan thu lèirmheas air na teirmichean ùra. review_link: Dèan lèirmheas air teirmichean na seirbheise title: Tha teirmichean na seirbheise aig %{domain} gu bhith atharrachadh + themes: + default: Mastodon time: formats: default: "%d %b %Y, %H:%M" @@ -2162,6 +2176,7 @@ gd: recovery_codes_regenerated: Chaidh na còdan aiseig ath-ghintinn recovery_instructions_html: Ma chailleas tu an t-inntrigeadh dhan fhòn agad, ’s urrainn dhut fear dhe na còdan aisig gu h-ìosal a chleachdadh airson faighinn a-steach dhan chunntas agad a-rithist. Cùm na còdan aisig sàbhailte. Mar eisimpleir, ’s urrainn dhut an clò-bhualadh ’s a chumail far a bheil thu a’ cumail na sgrìobhainnean cudromach eile agad. resume_app_authorization: Lean air ùghdarrachadh na h-aplacaid + role_requirement: Tha %{domain} ag iarraidh gun suidhich thu dearbhadh dà-cheumnach mus cleachd thu Mastodon. webauthn: Iuchraichean tèarainteachd unsubscriptions: create: diff --git a/config/locales/simple_form.gd.yml b/config/locales/simple_form.gd.yml index 85cca6346e2..d5093491c3b 100644 --- a/config/locales/simple_form.gd.yml +++ b/config/locales/simple_form.gd.yml @@ -134,6 +134,7 @@ gd: otp: 'Cuir a-steach an còd dà-cheumnach a ghin aplacaid an fhòn agad no cleachd fear dhe na còdan aisig agad:' webauthn: Mas e iuchair USB a th’ ann, dèan cinnteach gun cuir thu a-steach e is gun doir thu gnogag air ma bhios feum air sin. settings: + email_subscriptions: Ma chuireas tu seo à comas, cumaidh tu an luchd fo-sgrìobhaidh làithreach agad ach cha dèid puist-d a chur tuilleadh. indexable: Dh’fhaoidte gun nochd duilleag na pròifil agad am measg nan toraidhean luirg air Google, Bing is eile. show_application: Gidheadh, chì thu dè an aplacaid a dh’fhoillsich am post agad an-còmhnaidh. tag: @@ -166,6 +167,7 @@ gd: name: Ainm poblach na dreuchd ma chaidh a suidheachadh gun nochd i na baidse permissions_as_keys: Gheibh na cleachdaichean aig a bheil an dreuchd seo inntrigeadh dha… position: Ma tha còmhstri ann, buannaichidh an dreuchd as àirde ann an cuid a shuidheachaidhean. Tha gnìomhan sònraichte ann nach urrainn ach dreuchdan le prìomhachas ìosail a ghabhail + require_2fa: Feumaidh cleachdaichean aig a bheil an dreachd seo dearbhadh dà-cheumnach a shuidheachadh airson Mastodon a chleachdadh username_block: allow_with_approval: An àite bacadh clàraidh gu tur, bidh clàraidhean a mhaidsicheas feumach air d’ aonta comparison: Thoir an aire air an Scunthorpe Problem nuair a bhacas tu maidsichean pàirteach From 0742a01571649e7ca6dde43040959d6288d86c89 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 1 Apr 2026 12:04:59 +0200 Subject: [PATCH 07/20] Add MX record validation to e-mail subscriptions, refactor validator (#38502) --- app/models/email_subscription.rb | 1 + app/models/user.rb | 2 +- app/validators/email_mx_validator.rb | 16 +- spec/validators/email_mx_validator_spec.rb | 166 ++++++++++----------- 4 files changed, 89 insertions(+), 96 deletions(-) diff --git a/app/models/email_subscription.rb b/app/models/email_subscription.rb index a965f06e31f..fc3a0f125d1 100644 --- a/app/models/email_subscription.rb +++ b/app/models/email_subscription.rb @@ -20,6 +20,7 @@ class EmailSubscription < ApplicationRecord normalizes :email, with: ->(str) { str.squish.downcase } validates :email, presence: true, email_address: true, uniqueness: { scope: :account_id } + validates :email, email_mx: true, if: -> { email_changed? && !Rails.env.local? } scope :confirmed, -> { where.not(confirmed_at: nil) } scope :unconfirmed, -> { where(confirmed_at: nil) } diff --git a/app/models/user.rb b/app/models/user.rb index b4b9f9a9bf1..ac33324ddda 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -93,9 +93,9 @@ class User < ApplicationRecord validates :invite_request, presence: true, on: :create, if: :invite_text_required? validates :email, presence: true, email_address: true + validates :email, email_mx: { attempt_ip: :sign_up_ip }, if: :validate_email_dns? validates_with UserEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? } - validates_with EmailMxValidator, if: :validate_email_dns? validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create # Honeypot/anti-spam fields diff --git a/app/validators/email_mx_validator.rb b/app/validators/email_mx_validator.rb index f78b98d7dd7..d24b101a6e3 100644 --- a/app/validators/email_mx_validator.rb +++ b/app/validators/email_mx_validator.rb @@ -2,21 +2,21 @@ require 'resolv' -class EmailMxValidator < ActiveModel::Validator - def validate(user) - return if user.email.blank? +class EmailMxValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + return if value.blank? - domain = get_domain(user.email) + domain = get_domain(value) if domain.blank? || domain.include?('..') - user.errors.add(:email, :invalid) + record.errors.add(attribute, :invalid) elsif !on_allowlist?(domain) resolved_ips, resolved_domains = resolve_mx(domain) if resolved_ips.empty? - user.errors.add(:email, :unreachable) - elsif email_domain_blocked?(resolved_domains, user.sign_up_ip) - user.errors.add(:email, :blocked) + record.errors.add(attribute, :unreachable) + elsif email_domain_blocked?([domain, *resolved_domains], options[:attempt_ip].is_a?(Symbol) ? record.public_send(options[:attempt_ip]) : nil) + record.errors.add(attribute, :blocked) end end end diff --git a/spec/validators/email_mx_validator_spec.rb b/spec/validators/email_mx_validator_spec.rb index 0ffbf7fb8bc..e71794b8083 100644 --- a/spec/validators/email_mx_validator_spec.rb +++ b/spec/validators/email_mx_validator_spec.rb @@ -3,109 +3,101 @@ require 'rails_helper' RSpec.describe EmailMxValidator do - let(:user) { Fabricate.build :user, email: } - let(:email) { 'foo@example.com' } - let(:resolv_dns_double) { instance_double(Resolv::DNS) } + subject { record_class.new } - context 'with an e-mail domain that is explicitly allowed' do - around do |example| - original = Rails.configuration.x.email_domains_allowlist - Rails.configuration.x.email_domains_allowlist = 'example.com' - example.run - Rails.configuration.x.email_domains_allowlist = original - end + context 'with no options' do + let(:record_class) do + Class.new do + include ActiveModel::Validations - context 'when there are not DNS records' do - before { configure_resolver('example.com') } + def self.name = 'Record' - it 'does not add errors to record' do - subject.validate(user) - expect(user.errors).to be_empty + attr_accessor :email + + validates :email, email_mx: true end end - end - context 'when there are DNS records for the domain' do - before { configure_resolver('example.com', a: resolv_double_a('192.0.2.42')) } + let(:user) { Fabricate.build :user, email: } + let(:email) { 'foo@example.com' } + let(:resolv_dns_double) { instance_double(Resolv::DNS) } - it 'does not add errors to record' do - subject.validate(user) - expect(user.errors).to be_empty - end - end + context 'with an e-mail domain that is explicitly allowed' do + around do |example| + original = Rails.configuration.x.email_domains_allowlist + Rails.configuration.x.email_domains_allowlist = 'example.com' + example.run + Rails.configuration.x.email_domains_allowlist = original + end - context 'when the TagManager fails to normalize the domain' do - before do - allow(TagManager).to receive(:instance).and_return(tag_manage_double) - allow(tag_manage_double).to receive(:normalize_domain).with('example.com').and_raise(Addressable::URI::InvalidURIError) + context 'when there are not DNS records' do + before { configure_resolver('example.com') } + + it { is_expected.to allow_value(email).for(:email) } + end end - let(:tag_manage_double) { instance_double(TagManager) } + context 'when there are DNS records for the domain' do + before { configure_resolver('example.com', a: resolv_double_a('192.0.2.42')) } - it 'adds errors to record' do - subject.validate(user) - expect(user.errors).to be_present - end - end - - context 'when the email portion is blank' do - let(:email) { 'foo@' } - - it 'adds errors to record' do - subject.validate(user) - expect(user.errors).to be_present - end - end - - context 'when the email domain contains empty labels' do - let(:email) { 'foo@example..com' } - - before { configure_resolver('example..com', a: resolv_double_a('192.0.2.42')) } - - it 'adds errors to record' do - subject.validate(user) - expect(user.errors).to be_present - end - end - - context 'when there are no DNS records for the email domain' do - before { configure_resolver('example.com') } - - it 'adds errors to record' do - subject.validate(user) - expect(user.errors).to be_present - end - end - - context 'when MX record does not lead to an IP' do - before do - configure_resolver('example.com', mx: resolv_double_mx('mail.example.com')) - configure_resolver('mail.example.com') + it { is_expected.to allow_value(email).for(:email) } end - it 'adds errors to record' do - subject.validate(user) - expect(user.errors).to be_present - end - end + context 'when the TagManager fails to normalize the domain' do + before do + allow(TagManager).to receive(:instance).and_return(tag_manage_double) + allow(tag_manage_double).to receive(:normalize_domain).with('example.com').and_raise(Addressable::URI::InvalidURIError) + end - context 'when the MX record has an email domain block' do - before do - Fabricate :email_domain_block, domain: 'mail.example.com' - configure_resolver( - 'example.com', - mx: resolv_double_mx('mail.example.com') - ) - configure_resolver( - 'mail.example.com', - a: instance_double(Resolv::DNS::Resource::IN::A, address: '2.3.4.5'), - aaaa: instance_double(Resolv::DNS::Resource::IN::AAAA, address: 'fd00::2') - ) + let(:tag_manage_double) { instance_double(TagManager) } + + it { is_expected.to_not allow_value(email).for(:email) } end - it 'adds errors to record' do - subject.validate(user) - expect(user.errors).to be_present + context 'when the email portion is blank' do + let(:email) { 'foo@' } + + it { is_expected.to_not allow_value(email).for(:email) } + end + + context 'when the email domain contains empty labels' do + let(:email) { 'foo@example..com' } + + before { configure_resolver('example..com', a: resolv_double_a('192.0.2.42')) } + + it { is_expected.to_not allow_value(email).for(:email) } + end + + context 'when there are no DNS records for the email domain' do + before { configure_resolver('example.com') } + + it { is_expected.to_not allow_value(email).for(:email) } + end + + context 'when MX record does not lead to an IP' do + before do + configure_resolver('example.com', mx: resolv_double_mx('mail.example.com')) + configure_resolver('mail.example.com') + end + + it { is_expected.to_not allow_value(email).for(:email) } + end + + context 'when the MX record has an email domain block' do + before do + Fabricate :email_domain_block, domain: 'mail.example.com' + configure_resolver( + 'example.com', + mx: resolv_double_mx('mail.example.com') + ) + configure_resolver( + 'mail.example.com', + a: instance_double(Resolv::DNS::Resource::IN::A, address: '2.3.4.5'), + aaaa: instance_double(Resolv::DNS::Resource::IN::AAAA, address: 'fd00::2') + ) + end + + it { is_expected.to_not allow_value(email).for(:email) } end end From 0d20f2367ae48c5eb49b547c28afcc7e4d1349cd Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 1 Apr 2026 12:40:02 +0200 Subject: [PATCH 08/20] Profile redesign: Remove extra underline from number fields (#38509) --- .../mastodon/components/number_fields/styles.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/mastodon/components/number_fields/styles.module.scss b/app/javascript/mastodon/components/number_fields/styles.module.scss index 085bd5051dd..e61ace82350 100644 --- a/app/javascript/mastodon/components/number_fields/styles.module.scss +++ b/app/javascript/mastodon/components/number_fields/styles.module.scss @@ -23,6 +23,7 @@ a { padding: 0; + text-decoration: none; &:hover, &:focus { From 8733d09dc49ef0642599f5590fb3615390112ab5 Mon Sep 17 00:00:00 2001 From: MitarashiDango Date: Wed, 1 Apr 2026 19:40:54 +0900 Subject: [PATCH 09/20] Improve relative time display when post time is in the future (#38488) --- app/javascript/mastodon/components/status/header.tsx | 5 ++++- .../mastodon/features/account_timeline/v2/status_header.tsx | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/components/status/header.tsx b/app/javascript/mastodon/components/status/header.tsx index 65790bb4932..9b1445e9c1c 100644 --- a/app/javascript/mastodon/components/status/header.tsx +++ b/app/javascript/mastodon/components/status/header.tsx @@ -56,7 +56,10 @@ export const StatusHeader: FC = ({ className='status__relative-time' > - + {editedAt && } diff --git a/app/javascript/mastodon/features/account_timeline/v2/status_header.tsx b/app/javascript/mastodon/features/account_timeline/v2/status_header.tsx index 5f0ff886857..9958a47d965 100644 --- a/app/javascript/mastodon/features/account_timeline/v2/status_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/v2/status_header.tsx @@ -36,7 +36,10 @@ export const AccountStatusHeader: FC = ({ className='status__relative-time' > - + {editedAt && } From e9a051fcef90b2b03fb2f6e6f52f2abe3aa1f8a0 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 1 Apr 2026 14:59:34 +0200 Subject: [PATCH 10/20] Fix FetchRemoteKeyService possibly returning incorrect keys (#38511) --- .../activitypub/fetch_remote_key_service.rb | 21 ++++++++++++++----- .../fetch_remote_key_service_spec.rb | 4 ++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/services/activitypub/fetch_remote_key_service.rb b/app/services/activitypub/fetch_remote_key_service.rb index 7226f767379..e6f691b2880 100644 --- a/app/services/activitypub/fetch_remote_key_service.rb +++ b/app/services/activitypub/fetch_remote_key_service.rb @@ -9,12 +9,14 @@ class ActivityPub::FetchRemoteKeyService < BaseService def call(uri, suppress_errors: true) raise Error, 'No key URI given' if uri.blank? + @suppress_errors = suppress_errors + @uri = uri @json = fetch_resource(uri, false) raise Error, "Unable to fetch key JSON at #{uri}" if @json.nil? raise Error, "Unsupported JSON-LD context for document #{uri}" unless supported_context?(@json) || (supported_security_context?(@json) && @json['owner'].present? && !actor_type?) raise Error, "Unexpected object type for key #{uri}" unless expected_type? - return Keypair.from_legacy_account(find_actor(@json['id'], @json, suppress_errors), uri: uri) if actor_type? + return keypair_from_actor_json(@json['id'], @json) if actor_type? @owner = fetch_resource(owner_uri, true) @@ -23,8 +25,7 @@ class ActivityPub::FetchRemoteKeyService < BaseService raise Error, "Unexpected object type for actor #{owner_uri} (expected any of: #{SUPPORTED_TYPES})" unless expected_owner_type? raise Error, "publicKey id for #{owner_uri} does not correspond to #{@json['id']}" unless confirmed_owner? - # TODO: change to fetch and persist key - Keypair.from_legacy_account(find_actor(owner_uri, @owner, suppress_errors), uri: uri) + keypair_from_actor_json(owner_uri, @owner) rescue Error => e Rails.logger.debug { "Fetching key #{uri} failed: #{e.message}" } raise unless suppress_errors @@ -32,9 +33,19 @@ class ActivityPub::FetchRemoteKeyService < BaseService private - def find_actor(uri, prefetched_body, suppress_errors) + def keypair_from_actor_json(actor_uri, actor_json) + actor = find_actor(actor_uri, actor_json) + return if actor.nil? + + keypair = actor.keypairs.find_by(uri: @uri) + return keypair if keypair.present? + + Keypair.from_legacy_account(actor, uri: @uri) if actor.public_key.present? + end + + def find_actor(uri, prefetched_body) actor = ActivityPub::TagManager.instance.uri_to_actor(uri) - actor ||= ActivityPub::FetchRemoteActorService.new.call(uri, prefetched_body: prefetched_body, suppress_errors: suppress_errors) + actor ||= ActivityPub::FetchRemoteActorService.new.call(uri, prefetched_body: prefetched_body, suppress_errors: @suppress_errors) actor end diff --git a/spec/services/activitypub/fetch_remote_key_service_spec.rb b/spec/services/activitypub/fetch_remote_key_service_spec.rb index cd61ebee221..fc7dfb3c422 100644 --- a/spec/services/activitypub/fetch_remote_key_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_key_service_spec.rb @@ -64,6 +64,8 @@ RSpec.describe ActivityPub::FetchRemoteKeyService do it 'returns the expected account' do expect(keypair.account.uri).to eq 'https://example.com/alice' + expect(keypair.uri).to eq public_key_id + expect(keypair.public_key).to eq public_key_pem end end @@ -76,6 +78,8 @@ RSpec.describe ActivityPub::FetchRemoteKeyService do it 'returns the expected account' do expect(keypair.account.uri).to eq 'https://example.com/alice' + expect(keypair.uri).to eq public_key_id + expect(keypair.public_key).to eq public_key_pem end end From 1f7d6cde5e7d2e8ebb9a2882db0ce52a4853b407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Wed, 1 Apr 2026 15:22:19 +0200 Subject: [PATCH 11/20] Don't end account dropdown menu with a separator (#38481) --- .../features/account_timeline/components/menu.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/javascript/mastodon/features/account_timeline/components/menu.tsx b/app/javascript/mastodon/features/account_timeline/components/menu.tsx index cd1ec82e4fa..6ef03757029 100644 --- a/app/javascript/mastodon/features/account_timeline/components/menu.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/menu.tsx @@ -212,13 +212,14 @@ function currentMenuItems({ } if (isRemote) { - items.push( - { - text: intl.formatMessage(messages.openOriginalPage), - href: account.url, - }, - null, - ); + items.push({ + text: intl.formatMessage(messages.openOriginalPage), + href: account.url, + }); + + if (signedIn) { + items.push(null); + } } if (!signedIn) { From c2aafda610d352574491539bfff2ee500507f7c7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:22:46 +0200 Subject: [PATCH 12/20] Update dependency vite-plugin-svgr to v5 (#38480) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 4fb0a44a062..e5fb60017b5 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "vite": "^8.0.0", "vite-plugin-manifest-sri": "^0.2.0", "vite-plugin-pwa": "^1.2.0", - "vite-plugin-svgr": "^4.5.0", + "vite-plugin-svgr": "^5.0.0", "wicg-inert": "^3.1.2", "workbox-expiration": "^7.3.0", "workbox-routing": "^7.3.0", diff --git a/yarn.lock b/yarn.lock index 13e611512a4..02367d8ce24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2963,7 +2963,7 @@ __metadata: vite: "npm:^8.0.0" vite-plugin-manifest-sri: "npm:^0.2.0" vite-plugin-pwa: "npm:^1.2.0" - vite-plugin-svgr: "npm:^4.5.0" + vite-plugin-svgr: "npm:^5.0.0" vitest: "npm:^4.1.0" wicg-inert: "npm:^3.1.2" workbox-expiration: "npm:^7.3.0" @@ -14719,16 +14719,16 @@ __metadata: languageName: node linkType: hard -"vite-plugin-svgr@npm:^4.5.0": - version: 4.5.0 - resolution: "vite-plugin-svgr@npm:4.5.0" +"vite-plugin-svgr@npm:^5.0.0": + version: 5.0.0 + resolution: "vite-plugin-svgr@npm:5.0.0" dependencies: "@rollup/pluginutils": "npm:^5.2.0" "@svgr/core": "npm:^8.1.0" "@svgr/plugin-jsx": "npm:^8.1.0" peerDependencies: - vite: ">=2.6.0" - checksum: 10c0/3e1959fec626bb4f5a8ec13ff15bc40ffbc1c0ff38149bebe3f37dc2d67ed1f276f129ff7983e06946cf712e19996affd9d6868aa7d20d8921d1fe4449109b55 + vite: ">=3.0.0" + checksum: 10c0/8ebb90055589ee6a8a4cb7d78a10a92bef10732fbbe1528c2edf970e2f116cddd957456c9e560a0c004d24ce1111568f4f2498ed9d3cf37d49f696253ba9b4b2 languageName: node linkType: hard From 5a572f9c897686912e1197005485d3bd47d203a2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:24:06 +0200 Subject: [PATCH 13/20] Update dependency axios to v1.14.0 (#38466) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 02367d8ce24..6af5f5e0d4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5802,13 +5802,13 @@ __metadata: linkType: hard "axios@npm:^1.4.0": - version: 1.13.6 - resolution: "axios@npm:1.13.6" + version: 1.14.0 + resolution: "axios@npm:1.14.0" dependencies: follow-redirects: "npm:^1.15.11" form-data: "npm:^4.0.5" - proxy-from-env: "npm:^1.1.0" - checksum: 10c0/51fb5af055c3b85662fa97df17d986ae2c37d13bf86d50b6bb36b6b3a2dec6966a1d3a14ab3774b71707b155ae3597ed9b7babdf1a1a863d1a31840cb8e7ec71 + proxy-from-env: "npm:^2.1.0" + checksum: 10c0/2541f4aa215a7d1842429dad006fc682d82bc0e74bd14500823f7d8cce3bbae0e0a8c328c8538946718f366ab8ce5a4c12e9ad40e5a0f3482ff8bff0cd115d45 languageName: node linkType: hard @@ -11838,10 +11838,10 @@ __metadata: languageName: node linkType: hard -"proxy-from-env@npm:^1.1.0": - version: 1.1.0 - resolution: "proxy-from-env@npm:1.1.0" - checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58 languageName: node linkType: hard From 9b6354802a5e060c2a624d75858c3a7876f27277 Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 1 Apr 2026 15:45:10 +0200 Subject: [PATCH 14/20] Fix linting dev directories (#38514) --- stylelint.config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stylelint.config.js b/stylelint.config.js index 1db58de646d..94a1829f6fc 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -5,8 +5,7 @@ module.exports = { 'coverage/**/*', 'node_modules/**/*', 'public/assets/**/*', - 'public/packs/**/*', - 'public/packs-test/**/*', + 'public/packs*/**/*', 'vendor/**/*', ], reportDescriptionlessDisables: true, From e8f4896b08ca5f67911eb62f801afc976b4c2183 Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 1 Apr 2026 16:01:22 +0200 Subject: [PATCH 15/20] Profile redesign: Profile fields overflow (#38510) --- .../mastodon/features/account_edit/modals/fields_modals.tsx | 2 +- .../mastodon/features/account_edit/styles.module.scss | 5 +++++ .../features/account_timeline/modals/field_modal.tsx | 1 + .../features/account_timeline/modals/styles.module.css | 5 +++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/account_edit/modals/fields_modals.tsx b/app/javascript/mastodon/features/account_edit/modals/fields_modals.tsx index 0a4c64d9c4a..d0f67ddc62c 100644 --- a/app/javascript/mastodon/features/account_edit/modals/fields_modals.tsx +++ b/app/javascript/mastodon/features/account_edit/modals/fields_modals.tsx @@ -210,7 +210,7 @@ export const EditFieldModal = forwardRef< const labelStatus = checkField(newLabel); const valueStatus = checkField(newValue); - if (labelStatus || valueStatus) { + if (labelStatus?.variant === 'error' || valueStatus?.variant === 'error') { setFieldStatuses({ label: labelStatus ?? undefined, value: valueStatus ?? undefined, diff --git a/app/javascript/mastodon/features/account_edit/styles.module.scss b/app/javascript/mastodon/features/account_edit/styles.module.scss index cd3d34f31b7..1ca1e4646c3 100644 --- a/app/javascript/mastodon/features/account_edit/styles.module.scss +++ b/app/javascript/mastodon/features/account_edit/styles.module.scss @@ -182,6 +182,11 @@ // Field component +.fieldName, +.fieldValue { + word-break: break-all; +} + .fieldName { color: var(--color-text-secondary); font-size: 13px; diff --git a/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx b/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx index c778e08fa26..9c09be8e2bf 100644 --- a/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx +++ b/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx @@ -28,6 +28,7 @@ export const AccountFieldModal: FC<{ as='h2' htmlString={field.name_emojified} onElement={handleLabelElement} + className={classes.fieldName} /> Date: Wed, 1 Apr 2026 16:02:00 +0200 Subject: [PATCH 16/20] Fix jump when loading more media gallery items (#38512) --- app/javascript/mastodon/features/account_gallery/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/account_gallery/index.tsx b/app/javascript/mastodon/features/account_gallery/index.tsx index 2a69342a467..0a6eb1c1f1e 100644 --- a/app/javascript/mastodon/features/account_gallery/index.tsx +++ b/app/javascript/mastodon/features/account_gallery/index.tsx @@ -225,7 +225,7 @@ export const AccountGallery: React.FC<{ alwaysPrepend append={accountId && } scrollKey='account_gallery' - showLoading={isLoading} + isLoading={isLoading} hasMore={!forceEmptyState && hasMore} onLoadMore={handleLoadMore} emptyMessage={emptyMessage} From db704180b2eaeaefef61f6b232ba4c6f57aa3300 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 1 Apr 2026 16:14:53 +0200 Subject: [PATCH 17/20] Fix signature verification when the key ID is an `acct:` URI (#38516) --- .../concerns/signature_verification.rb | 11 ++++++++- spec/requests/signature_verification_spec.rb | 24 ++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index f752e5cd93c..49b72872390 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -101,7 +101,7 @@ module SignatureVerification end if key_id.start_with?('acct:') - stoplight_wrapper.run { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) } + stoplight_wrapper.run { fetch_key_from_acct(key_id.delete_prefix('acct:')) } elsif !ActivityPub::TagManager.instance.local_uri?(key_id) keypair = Keypair.from_keyid(key_id) return keypair if keypair.present? @@ -114,6 +114,15 @@ module SignatureVerification raise Mastodon::SignatureVerificationError, e.message end + def fetch_key_from_acct(acct) + # This is legacy and can't let us pick a specific key, so pick the first + + account = ResolveAccountService.new.call(acct, suppress_errors: false) + return if account.nil? + + account.keypairs.first || Keypair.from_legacy_account(account) + end + def stoplight_wrapper Stoplight( "source:#{request.remote_ip}", diff --git a/spec/requests/signature_verification_spec.rb b/spec/requests/signature_verification_spec.rb index 3119138a0a0..1391aeb7504 100644 --- a/spec/requests/signature_verification_spec.rb +++ b/spec/requests/signature_verification_spec.rb @@ -71,7 +71,29 @@ RSpec.describe 'signature verification concern' do context 'with an HTTP Signature (draft version)' do context 'with a known account' do - let!(:actor) { Fabricate(:account, domain: 'remote.domain', uri: 'https://remote.domain/users/bob', private_key: nil, public_key: actor_keypair.public_key.to_pem) } + let!(:actor) { Fabricate(:account, username: 'bob', domain: 'remote.domain', uri: 'https://remote.domain/users/bob', private_key: nil, public_key: actor_keypair.public_key.to_pem) } + + context 'with an acct key ID' do + let(:signature_header) do + 'keyId="acct:bob@remote.domain",algorithm="rsa-sha256",headers="date host (request-target)",signature="Z8ilar3J7bOwqZkMp7sL8sRs4B1FT+UorbmvWoE+A5UeoOJ3KBcUmbsh+k3wQwbP5gMNUrra9rEWabpasZGphLsbDxfbsWL3Cf0PllAc7c1c7AFEwnewtExI83/qqgEkfWc2z7UDutXc2NfgAx89Ox8DXU/fA2GG0jILjB6UpFyNugkY9rg6oI31UnvfVi3R7sr3/x8Ea3I9thPvqI2byF6cojknSpDAwYzeKdngX3TAQEGzFHz3SDWwyp3jeMWfwvVVbM38FxhvAnSumw7YwWW4L7M7h4M68isLimoT3yfCn2ucBVL5Dz8koBpYf/40w7QidClAwCafZQFC29yDOg=="' # rubocop:disable Layout/LineLength + end + + it 'successfuly verifies signature', :aggregate_failures do + expect(signature_header).to eq build_signature_string(actor_keypair, 'acct:bob@remote.domain', 'get /activitypub/success', { 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Host' => 'www.example.com' }) + + get '/activitypub/success', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Signature' => signature_header, + } + + expect(response).to have_http_status(200) + expect(response.parsed_body).to match( + signed_request: true, + signature_actor_id: actor.id.to_s + ) + end + end context 'with a valid signature on a GET request' do let(:signature_header) do From 93dcca7f12456f446f1e946602ec931fc0c3e48f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 1 Apr 2026 16:24:28 +0200 Subject: [PATCH 18/20] Add email subscriptions to profiles in web UI (#38487) --- .../email_subscriptions_controller.rb | 2 + app/javascript/mastodon/api/accounts.ts | 3 + app/javascript/mastodon/api_types/accounts.ts | 1 + app/javascript/mastodon/api_types/errors.ts | 22 ++ .../components/account_header.tsx | 6 + .../components/account_subscription_form.tsx | 207 ++++++++++++++++++ .../components/redesign.module.scss | 62 ++++++ app/javascript/mastodon/locales/en.json | 9 + app/javascript/mastodon/models/account.ts | 1 + stylelint.config.js | 7 + 10 files changed, 320 insertions(+) create mode 100644 app/javascript/mastodon/api_types/errors.ts create mode 100644 app/javascript/mastodon/features/account_timeline/components/account_subscription_form.tsx diff --git a/app/controllers/api/v1/accounts/email_subscriptions_controller.rb b/app/controllers/api/v1/accounts/email_subscriptions_controller.rb index dcdd41f6db9..4e773f902bc 100644 --- a/app/controllers/api/v1/accounts/email_subscriptions_controller.rb +++ b/app/controllers/api/v1/accounts/email_subscriptions_controller.rb @@ -8,6 +8,8 @@ class Api::V1::Accounts::EmailSubscriptionsController < Api::BaseController def create @account.email_subscriptions.create!(email: params[:email], locale: I18n.locale) render_empty + rescue ActiveRecord::RecordInvalid => e + render json: ValidationErrorFormatter.new(e).as_json, status: 422 end private diff --git a/app/javascript/mastodon/api/accounts.ts b/app/javascript/mastodon/api/accounts.ts index fc6e38fbc8d..2229d17c560 100644 --- a/app/javascript/mastodon/api/accounts.ts +++ b/app/javascript/mastodon/api/accounts.ts @@ -75,3 +75,6 @@ export const apiDeleteProfileAvatar = () => export const apiDeleteProfileHeader = () => apiRequestDelete('v1/profile/header'); + +export const apiSubscribeByEmail = (id: string, email: string) => + apiRequestPost(`v1/accounts/${id}/email_subscriptions`, { email }); diff --git a/app/javascript/mastodon/api_types/accounts.ts b/app/javascript/mastodon/api_types/accounts.ts index 351f3245cc6..0a5e847e8ea 100644 --- a/app/javascript/mastodon/api_types/accounts.ts +++ b/app/javascript/mastodon/api_types/accounts.ts @@ -68,6 +68,7 @@ export interface BaseApiAccountJSON { limited?: boolean; memorial?: boolean; hide_collections: boolean; + email_subscriptions?: boolean; } // See app/serializers/rest/muted_account_serializer.rb diff --git a/app/javascript/mastodon/api_types/errors.ts b/app/javascript/mastodon/api_types/errors.ts new file mode 100644 index 00000000000..46f8e0b8cda --- /dev/null +++ b/app/javascript/mastodon/api_types/errors.ts @@ -0,0 +1,22 @@ +export type ErrorToken = + | 'ERR_TAKEN' + | 'ERR_INVALID' + | 'ERR_BLOCKED' + | 'ERR_RESERVED' + | 'ERR_TOO_MANY' + | 'ERR_MALFORMED' + | 'ERR_UNUSABLE' + | 'ERR_TOO_SOON' + | 'ERR_BELOW_LIMIT' + | 'ERR_UNREACHABLE' + | 'ERR_ELEVATED'; + +export interface ValidationError { + error: ErrorToken; + description: string; +} + +export interface ValidationErrorResponse { + error: string; + details: Record; +} diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx index 6a9d51f737c..4f4c1663e85 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -23,6 +23,7 @@ import { useAppSelector, useAppDispatch } from '@/mastodon/store'; import { isRedesignEnabled } from '../common'; import { AccountName } from './account_name'; +import { AccountSubscriptionForm } from './account_subscription_form'; import { AccountBadges } from './badges'; import { AccountButtons } from './buttons'; import { FamiliarFollowers } from './familiar_followers'; @@ -218,9 +219,14 @@ export const AccountHeader: React.FC<{ isRedesign && redesignClasses.bio, )} /> + + {!me && account.email_subscriptions && ( + + )} + )} diff --git a/app/javascript/mastodon/features/account_timeline/components/account_subscription_form.tsx b/app/javascript/mastodon/features/account_timeline/components/account_subscription_form.tsx new file mode 100644 index 00000000000..d183aa1cb30 --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/account_subscription_form.tsx @@ -0,0 +1,207 @@ +import { useState, useCallback, useId } from 'react'; + +import { FormattedMessage, useIntl, defineMessages } from 'react-intl'; +import type { IntlShape } from 'react-intl'; + +import { Link } from 'react-router-dom'; + +import { AxiosError } from 'axios'; + +import { apiSubscribeByEmail } from 'mastodon/api/accounts'; +import type { + ValidationErrorResponse, + ValidationError, +} from 'mastodon/api_types/errors'; +import { A11yLiveRegion } from 'mastodon/components/a11y_live_region'; +import { Button } from 'mastodon/components/button'; +import { CalloutInline } from 'mastodon/components/callout_inline'; +import { DisplayName } from 'mastodon/components/display_name'; +import type { FieldStatus } from 'mastodon/components/form_fields'; +import formFieldClasses from 'mastodon/components/form_fields/form_field_wrapper.module.scss'; +import { TextInput } from 'mastodon/components/form_fields/text_input_field'; +import { useAppSelector } from 'mastodon/store'; + +import classes from './redesign.module.scss'; + +const messages = defineMessages({ + emailInvalid: { + id: 'email_subscriptions.validation.email.invalid', + defaultMessage: 'Invalid email address', + }, + emailBlocked: { + id: 'email_subscriptions.validation.email.blocked', + defaultMessage: 'Blocked email provider', + }, + email: { + id: 'email_subscriptions.email', + defaultMessage: 'Email address', + }, +}); + +const isValidationErrorResponse = ( + data: unknown, +): data is ValidationErrorResponse => + typeof data === 'object' && + data !== null && + 'error' in data && + 'details' in data; + +const fieldStatusFromErrors = ( + intl: IntlShape, + errors: ValidationError[], +): FieldStatus | undefined => { + const error = errors[0]; + + if (!error) { + return undefined; + } + + let message: string; + + switch (error.error) { + case 'ERR_BLOCKED': + message = intl.formatMessage(messages.emailBlocked); + break; + case 'ERR_INVALID': + default: + message = intl.formatMessage(messages.emailInvalid); + break; + } + + return { variant: 'error', message }; +}; + +export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({ + accountId, +}) => { + const account = useAppSelector((state) => state.accounts.get(accountId)); + const intl = useIntl(); + const accessibilityId = useId(); + + const [email, setEmail] = useState(''); + const [submitting, setSubmitting] = useState(false); + const [submitted, setSubmitted] = useState(false); + const [errors, setErrors] = useState>({}); + + const handleChange = useCallback>( + (e) => { + setEmail(e.target.value); + setErrors({}); + }, + [], + ); + + const handleSubmit = useCallback( + (e) => { + e.preventDefault(); + + if (email.length === 0) { + return; + } + + setSubmitting(true); + + apiSubscribeByEmail(accountId, email) + .then(() => { + setSubmitting(false); + setSubmitted(true); + + return ''; + }) + .catch((err: unknown) => { + setSubmitting(false); + + if (err instanceof AxiosError && err.response) { + const data: unknown = err.response.data; + + if (isValidationErrorResponse(data)) { + if (data.details.email?.some((k) => k.error === 'ERR_TAKEN')) { + setSubmitted(true); + return; + } + + setErrors(data.details); + } + } + }); + }, + [accountId, email], + ); + + if (submitted) { + return ( +
+
+

+ +

+ +
+
+ ); + } + + return ( +
+
+

+ , + }} + /> +

+ +
+ +
+
+ + + + {errors.email && ( + + )} + +
+ + +
+ +
+ {str} }} + /> +
+
+ ); +}; diff --git a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss index 391a6ac7fe7..11446104a35 100644 --- a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -391,3 +391,65 @@ svg.badgeIcon { padding-bottom: 14px; } } + +.bannerBase { + box-sizing: border-box; + padding: 16px; + border-radius: 12px; + background: var(--color-bg-secondary); + display: flex; + flex-direction: column; + gap: 12px; + justify-content: center; + align-items: flex-start; + margin: 16px 0; +} + +.bannerTextAndActions { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 13px; + font-weight: 400; + color: var(--color-text-primary); + + h2 { + font-size: 17px; + font-weight: 600; + } +} + +.bannerDisclaimer { + color: var(--color-text-secondary); + font-size: 11px; + + a { + color: inherit; + } +} + +.bannerBaseCentered { + composes: bannerBase; + min-height: 146px; + align-items: center; + + .bannerTextAndActions { + text-align: center; + } +} + +.bannerInputButton { + display: flex; + gap: 8px; + align-self: stretch; + align-items: flex-start; + + & > div { + flex-grow: 1; + } + + input[type='email'] { + padding: 7px 8px; // To align size with button + background: var(--color-bg-primary); + } +} diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index a7520348300..19fc3c3efed 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -581,6 +581,15 @@ "domain_pill.your_server": "Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers, too.", "domain_pill.your_username": "Your unique identifier on this server. It’s possible to find users with the same username on different servers.", "dropdown.empty": "Select an option", + "email_subscriptions.email": "Email address", + "email_subscriptions.form.action": "Subscribe", + "email_subscriptions.form.disclaimer": "You can unsubscribe at any time. For more information, refer to the Privacy Policy.", + "email_subscriptions.form.lead": "Get posts in your inbox without creating a Mastodon account.", + "email_subscriptions.form.title": "Sign up for email updates from {name}", + "email_subscriptions.submitted.lead": "Check your inbox for an email to finish signing up for email updates.", + "email_subscriptions.submitted.title": "One more step", + "email_subscriptions.validation.email.blocked": "Blocked email provider", + "email_subscriptions.validation.email.invalid": "Invalid email address", "embed.instructions": "Embed this post on your website by copying the code below.", "embed.preview": "Here is what it will look like:", "emoji_button.activity": "Activity", diff --git a/app/javascript/mastodon/models/account.ts b/app/javascript/mastodon/models/account.ts index 6248d8e97b6..f13d1c68312 100644 --- a/app/javascript/mastodon/models/account.ts +++ b/app/javascript/mastodon/models/account.ts @@ -101,6 +101,7 @@ export const accountDefaultValues: AccountShape = { limited: false, moved: null, hide_collections: false, + email_subscriptions: false, // This comes from `ApiMutedAccountJSON`, but we should eventually // store that in a different object. mute_expires_at: null, diff --git a/stylelint.config.js b/stylelint.config.js index 94a1829f6fc..10a2f1cd555 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -50,6 +50,13 @@ module.exports = { true, { ignorePseudoClasses: ['global'] }, ], + + 'property-no-unknown': [ + true, + { + ignoreProperties: ['composes'], + }, + ], }, }, ], From 159d710bc196dbfb4c75cb18de5b5eab95f90268 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:44:43 +0200 Subject: [PATCH 19/20] Update dependency use-debounce to v10.1.1 (#38484) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6af5f5e0d4d..147390ffaf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14608,11 +14608,11 @@ __metadata: linkType: hard "use-debounce@npm:^10.0.0": - version: 10.1.0 - resolution: "use-debounce@npm:10.1.0" + version: 10.1.1 + resolution: "use-debounce@npm:10.1.1" peerDependencies: react: "*" - checksum: 10c0/1d2c9ab71be283f7ea9f9c78f3574aeb6ff6fbcb18a9c5daf7f633521a8978f14d190016d39fd773227e40e9929e223677bb311343dadf33ee0763ef24bff510 + checksum: 10c0/0d1b2ff16447651c92ef444be3b7e29608e229c169a90e7cbd1ef13775475734b0910eaf01f2f64dc9f2b1d5dd8cf03042c5a09e230c9bb2ee148a18b5bba074 languageName: node linkType: hard From ca5c0a144ade4bff7bd10446a39d86116879884e Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 1 Apr 2026 17:03:43 +0200 Subject: [PATCH 20/20] Profile redesign: Remove feature flag (#38513) --- .../mastodon/components/follow_button.tsx | 18 +- .../features/account_featured/index.tsx | 10 +- .../features/account_gallery/index.tsx | 7 +- .../features/account_timeline/common.ts | 5 - .../components/account_header.tsx | 81 ++--- .../components/account_name.tsx | 31 -- .../account_timeline/components/badges.tsx | 25 +- .../account_timeline/components/buttons.tsx | 3 - .../account_timeline/components/fields.tsx | 61 +--- .../account_timeline/components/menu.tsx | 280 +----------------- .../components/number_fields.tsx | 62 +--- .../account_timeline/components/tabs.tsx | 29 +- .../features/account_timeline/v2/filters.tsx | 2 +- .../account_timeline/v2/pinned_statuses.tsx | 5 - app/javascript/mastodon/features/ui/index.jsx | 18 +- .../features/ui/util/async-components.js | 7 +- app/javascript/mastodon/locales/en.json | 1 - app/javascript/mastodon/utils/environment.ts | 2 +- spec/system/account_notes_spec.rb | 48 --- 19 files changed, 67 insertions(+), 628 deletions(-) delete mode 100644 spec/system/account_notes_spec.rb diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx index 14f2d62d9aa..4f8b1b0bea2 100644 --- a/app/javascript/mastodon/components/follow_button.tsx +++ b/app/javascript/mastodon/components/follow_button.tsx @@ -6,7 +6,6 @@ import classNames from 'classnames'; import { Link } from 'react-router-dom'; import { useIdentity } from '@/mastodon/identity_context'; -import { isServerFeatureEnabled } from '@/mastodon/utils/environment'; import { fetchRelationships, followAccount, @@ -171,23 +170,10 @@ export const FollowButton: React.FC<{ 'button--compact': compact, }); - if (isServerFeatureEnabled('profile_redesign')) { - return ( - - {label} - - ); - } - return ( - + {label} - + ); } diff --git a/app/javascript/mastodon/features/account_featured/index.tsx b/app/javascript/mastodon/features/account_featured/index.tsx index 0d9adfe3146..937a200e1cc 100644 --- a/app/javascript/mastodon/features/account_featured/index.tsx +++ b/app/javascript/mastodon/features/account_featured/index.tsx @@ -7,7 +7,6 @@ import { useHistory } from 'react-router'; import { List as ImmutableList } from 'immutable'; import { useAccount } from '@/mastodon/hooks/useAccount'; -import { isServerFeatureEnabled } from '@/mastodon/utils/environment'; import { fetchEndorsedAccounts } from 'mastodon/actions/accounts'; import { fetchFeaturedTags } from 'mastodon/actions/featured_tags'; import { Account } from 'mastodon/components/account'; @@ -49,11 +48,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ const history = useHistory(); useEffect(() => { - if ( - account && - !account.show_featured && - isServerFeatureEnabled('profile_redesign') - ) { + if (account && !account.show_featured) { history.push(`/@${account.acct}`); } }, [account, history]); @@ -111,8 +106,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ ); } - const noTags = - featuredTags.isEmpty() || isServerFeatureEnabled('profile_redesign'); + const noTags = featuredTags.isEmpty(); if ( noTags && diff --git a/app/javascript/mastodon/features/account_gallery/index.tsx b/app/javascript/mastodon/features/account_gallery/index.tsx index 0a6eb1c1f1e..069f1242f70 100644 --- a/app/javascript/mastodon/features/account_gallery/index.tsx +++ b/app/javascript/mastodon/features/account_gallery/index.tsx @@ -4,7 +4,6 @@ import { FormattedMessage } from 'react-intl'; import { List as ImmutableList, isList } from 'immutable'; -import { isServerFeatureEnabled } from '@/mastodon/utils/environment'; import { openModal } from 'mastodon/actions/modal'; import { expandAccountMediaTimeline } from 'mastodon/actions/timelines'; import { ColumnBackButton } from 'mastodon/components/column_back_button'; @@ -27,8 +26,6 @@ import { MediaItem } from './components/media_item'; const emptyList = ImmutableList(); -const redesignEnabled = isServerFeatureEnabled('profile_redesign'); - const selectGalleryTimeline = createAppSelector( [ (_state, accountId?: string | null) => accountId, @@ -58,7 +55,7 @@ const selectGalleryTimeline = createAppSelector( const { show_media, show_media_replies } = account; // If the account disabled showing media, don't display anything. - if (!show_media && redesignEnabled) { + if (!show_media) { return { items, hasMore: false, @@ -67,7 +64,7 @@ const selectGalleryTimeline = createAppSelector( }; } - const withReplies = show_media_replies && redesignEnabled; + const withReplies = show_media_replies; const timeline = timelines.get( `account:${accountId}:media${withReplies ? ':with_replies' : ''}`, ); diff --git a/app/javascript/mastodon/features/account_timeline/common.ts b/app/javascript/mastodon/features/account_timeline/common.ts index 7a939bbec9d..b036530e14d 100644 --- a/app/javascript/mastodon/features/account_timeline/common.ts +++ b/app/javascript/mastodon/features/account_timeline/common.ts @@ -1,9 +1,4 @@ import type { AccountFieldShape } from '@/mastodon/models/account'; -import { isServerFeatureEnabled } from '@/mastodon/utils/environment'; - -export function isRedesignEnabled() { - return isServerFeatureEnabled('profile_redesign'); -} export interface AccountField extends AccountFieldShape { nameHasEmojis: boolean; diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx index 4f4c1663e85..90b0127e15d 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -7,7 +7,6 @@ import { openModal } from '@/mastodon/actions/modal'; import { AccountBio } from '@/mastodon/components/account_bio'; import { Avatar } from '@/mastodon/components/avatar'; import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; -import { AccountNote } from '@/mastodon/features/account/components/account_note'; import FollowRequestNoteContainer from '@/mastodon/features/account/containers/follow_request_note_container'; import { useLayout } from '@/mastodon/hooks/useLayout'; import { useVisibility } from '@/mastodon/hooks/useVisibility'; @@ -20,15 +19,12 @@ import type { Account } from '@/mastodon/models/account'; import { getAccountHidden } from '@/mastodon/selectors/accounts'; import { useAppSelector, useAppDispatch } from '@/mastodon/store'; -import { isRedesignEnabled } from '../common'; - import { AccountName } from './account_name'; import { AccountSubscriptionForm } from './account_subscription_form'; import { AccountBadges } from './badges'; import { AccountButtons } from './buttons'; import { FamiliarFollowers } from './familiar_followers'; import { AccountHeaderFields } from './fields'; -import { AccountInfo } from './info'; import { MemorialNote } from './memorial_note'; import { MovedNote } from './moved_note'; import { AccountNote as AccountNoteRedesign } from './note'; @@ -52,8 +48,6 @@ export const AccountHeader: React.FC<{ accountId: string; hideTabs?: boolean; }> = ({ accountId, hideTabs }) => { - const isRedesign = isRedesignEnabled(); - const dispatch = useAppDispatch(); const account = useAppSelector((state) => state.accounts.get(accountId)); const relationship = useAppSelector((state) => @@ -120,13 +114,9 @@ export const AccountHeader: React.FC<{
- {me !== account.id && relationship && !isRedesign && ( - - )} - {!suspendedOrHidden && (
- - {!isRedesign && ( - - )}
- {isRedesign && ( - - )} +
@@ -192,31 +173,19 @@ export const AccountHeader: React.FC<{ )} - {!isRedesign && ( - - )} - {!suspendedOrHidden && (
- {me && - account.id !== me && - (isRedesign ? ( - - ) : ( - - ))} + {me && account.id !== me && ( + + )} @@ -231,20 +200,18 @@ export const AccountHeader: React.FC<{
)} - {isRedesign && ( - - )} +
- {!hideTabs && !hidden && } + {!hideTabs && !hidden && }
diff --git a/app/javascript/mastodon/features/account_timeline/components/account_name.tsx b/app/javascript/mastodon/features/account_timeline/components/account_name.tsx index ac6ab2735e2..3fd9e30c286 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_name.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_name.tsx @@ -14,10 +14,6 @@ import { useAppSelector } from '@/mastodon/store'; import AtIcon from '@/material-icons/400-24px/alternate_email.svg?react'; import HelpIcon from '@/material-icons/400-24px/help.svg?react'; import DomainIcon from '@/material-icons/400-24px/language.svg?react'; -import LockIcon from '@/material-icons/400-24px/lock.svg?react'; - -import { DomainPill } from '../../account/components/domain_pill'; -import { isRedesignEnabled } from '../common'; import classes from './redesign.module.scss'; @@ -34,7 +30,6 @@ const messages = defineMessages({ }); export const AccountName: FC<{ accountId: string }> = ({ accountId }) => { - const intl = useIntl(); const account = useAccount(accountId); const me = useAppSelector((state) => state.meta.get('me') as string); const localDomain = useAppSelector( @@ -47,32 +42,6 @@ export const AccountName: FC<{ accountId: string }> = ({ accountId }) => { const [username = '', domain = localDomain] = account.acct.split('@'); - if (!isRedesignEnabled()) { - return ( -

- - - - @{username} - @{domain} - - - {account.locked && ( - - )} - -

- ); - } - return (

diff --git a/app/javascript/mastodon/features/account_timeline/components/badges.tsx b/app/javascript/mastodon/features/account_timeline/components/badges.tsx index 9bfc3b5da53..75e0c31a183 100644 --- a/app/javascript/mastodon/features/account_timeline/components/badges.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/badges.tsx @@ -20,8 +20,6 @@ import { useAccount } from '@/mastodon/hooks/useAccount'; import type { AccountRole } from '@/mastodon/models/account'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; -import { isRedesignEnabled } from '../common'; - import classes from './redesign.module.scss'; export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { @@ -46,9 +44,6 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { return null; } - const isRedesign = isRedesignEnabled(); - const className = isRedesign ? classes.badge : ''; - const domain = account.acct.includes('@') ? account.acct.split('@')[1] : localDomain; @@ -58,7 +53,7 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { , @@ -68,8 +63,8 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { , ); @@ -77,17 +72,17 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { }); if (account.bot) { - badges.push(); + badges.push(); } if (account.group) { - badges.push(); + badges.push(); } - if (isRedesign && relationship) { + if (relationship) { if (relationship.blocking) { badges.push( , ); } @@ -95,7 +90,7 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { badges.push( = ({ accountId }) => { badges.push( , ); @@ -136,5 +131,5 @@ export const PinnedBadge: FC = () => ( function isAdminBadge(role: AccountRole) { const name = role.name.toLowerCase(); - return isRedesignEnabled() && (name === 'admin' || name === 'owner'); + return name === 'admin' || name === 'owner'; } diff --git a/app/javascript/mastodon/features/account_timeline/components/buttons.tsx b/app/javascript/mastodon/features/account_timeline/components/buttons.tsx index fe0bbbfbd62..0c24207b6ab 100644 --- a/app/javascript/mastodon/features/account_timeline/components/buttons.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/buttons.tsx @@ -16,8 +16,6 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react'; import ShareIcon from '@/material-icons/400-24px/share.svg?react'; -import { isRedesignEnabled } from '../common'; - import { AccountMenu } from './menu'; const messages = defineMessages({ @@ -97,7 +95,6 @@ const AccountButtonsOther: FC< accountId={accountId} className='account__header__follow-button' labelLength='long' - withUnmute={!isRedesignEnabled()} /> )} {isFollowing && ( diff --git a/app/javascript/mastodon/features/account_timeline/components/fields.tsx b/app/javascript/mastodon/features/account_timeline/components/fields.tsx index 5bad90aaaeb..80d6cc7f679 100644 --- a/app/javascript/mastodon/features/account_timeline/components/fields.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/fields.tsx @@ -1,68 +1,30 @@ import { useCallback, useMemo, useRef, useState } from 'react'; import type { FC } from 'react'; -import { defineMessage, FormattedMessage, useIntl } from 'react-intl'; +import { defineMessage, useIntl } from 'react-intl'; import classNames from 'classnames'; import IconVerified from '@/images/icons/icon_verified.svg?react'; import { openModal } from '@/mastodon/actions/modal'; -import { AccountFields } from '@/mastodon/components/account_fields'; import { CustomEmojiProvider } from '@/mastodon/components/emoji/context'; import type { EmojiHTMLProps } from '@/mastodon/components/emoji/html'; import { EmojiHTML } from '@/mastodon/components/emoji/html'; -import { FormattedDateWrapper } from '@/mastodon/components/formatted_date'; import { Icon } from '@/mastodon/components/icon'; import { IconButton } from '@/mastodon/components/icon_button'; import { MiniCard } from '@/mastodon/components/mini_card'; import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; import { useAccount } from '@/mastodon/hooks/useAccount'; import { useResizeObserver } from '@/mastodon/hooks/useObserver'; -import type { Account } from '@/mastodon/models/account'; import { useAppDispatch } from '@/mastodon/store'; import MoreIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { cleanExtraEmojis } from '../../emoji/normalize'; import type { AccountField } from '../common'; -import { isRedesignEnabled } from '../common'; import { useFieldHtml } from '../hooks/useFieldHtml'; import classes from './redesign.module.scss'; -export const AccountHeaderFields: FC<{ accountId: string }> = ({ - accountId, -}) => { - const account = useAccount(accountId); - - if (!account) { - return null; - } - - if (isRedesignEnabled()) { - return ; - } - - return ( -
-
-
- -
-
- -
-
- - -
- ); -}; - const verifyMessage = defineMessage({ id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}', @@ -75,13 +37,22 @@ const dateFormatOptions: Intl.DateTimeFormatOptions = { minute: '2-digit', }; -const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => { +export const AccountHeaderFields: FC<{ accountId: string }> = ({ + accountId, +}) => { + const account = useAccount(accountId); + const emojis = useMemo( - () => cleanExtraEmojis(account.emojis), - [account.emojis], + () => cleanExtraEmojis(account?.emojis), + [account?.emojis], ); + const accountFields = account?.fields; const fields: AccountField[] = useMemo(() => { - const fields = account.fields.toJS(); + const fields = accountFields?.toJS(); + if (!fields) { + return []; + } + if (!emojis) { return fields.map((field) => ({ ...field, @@ -102,10 +73,10 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => { field.value_plain?.includes(`:${code}:`), ), })); - }, [account.fields, emojis]); + }, [accountFields, emojis]); const htmlHandlers = useElementHandledLink({ - hashtagAccountId: account.id, + hashtagAccountId: account?.id, }); const { wrapperRef } = useColumnWrap(); diff --git a/app/javascript/mastodon/features/account_timeline/components/menu.tsx b/app/javascript/mastodon/features/account_timeline/components/menu.tsx index 6ef03757029..24ef0912976 100644 --- a/app/javascript/mastodon/features/account_timeline/components/menu.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/menu.tsx @@ -40,8 +40,6 @@ import PersonRemoveIcon from '@/material-icons/400-24px/person_remove.svg?react' import ReportIcon from '@/material-icons/400-24px/report.svg?react'; import ShareIcon from '@/material-icons/400-24px/share.svg?react'; -import { isRedesignEnabled } from '../common'; - import classes from './redesign.module.scss'; export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => { @@ -63,19 +61,9 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => { return []; } - if (isRedesignEnabled()) { - return redesignMenuItems({ - account, - signedIn: !isMe && signedIn, - permissions, - intl, - relationship, - dispatch, - }); - } - return currentMenuItems({ + return redesignMenuItems({ account, - signedIn, + signedIn: !isMe && signedIn, permissions, intl, relationship, @@ -178,270 +166,6 @@ const messages = defineMessages({ }, }); -function currentMenuItems({ - account, - signedIn, - permissions, - intl, - relationship, - dispatch, -}: MenuItemsParams): MenuItem[] { - const items: MenuItem[] = []; - const isRemote = account.acct !== account.username; - - if (signedIn && !account.suspended) { - items.push( - { - text: intl.formatMessage(messages.mention, { - name: account.username, - }), - action: () => { - dispatch(mentionCompose(account)); - }, - }, - { - text: intl.formatMessage(messages.direct, { - name: account.username, - }), - action: () => { - dispatch(directCompose(account)); - }, - }, - null, - ); - } - - if (isRemote) { - items.push({ - text: intl.formatMessage(messages.openOriginalPage), - href: account.url, - }); - - if (signedIn) { - items.push(null); - } - } - - if (!signedIn) { - return items; - } - - if (relationship?.following) { - // Timeline options - if (!relationship.muting) { - if (relationship.showing_reblogs) { - items.push({ - text: intl.formatMessage(messages.hideReblogs, { - name: account.username, - }), - action: () => { - dispatch(followAccount(account.id, { reblogs: false })); - }, - }); - } else { - items.push({ - text: intl.formatMessage(messages.showReblogs, { - name: account.username, - }), - action: () => { - dispatch(followAccount(account.id, { reblogs: true })); - }, - }); - } - - items.push( - { - text: intl.formatMessage(messages.languages), - action: () => { - dispatch( - openModal({ - modalType: 'SUBSCRIBED_LANGUAGES', - modalProps: { - accountId: account.id, - }, - }), - ); - }, - }, - null, - ); - } - - items.push( - { - text: intl.formatMessage( - relationship.endorsed ? messages.unendorse : messages.endorse, - ), - action: () => { - if (relationship.endorsed) { - dispatch(unpinAccount(account.id)); - } else { - dispatch(pinAccount(account.id)); - } - }, - }, - { - text: intl.formatMessage(messages.add_or_remove_from_list), - action: () => { - dispatch( - openModal({ - modalType: 'LIST_ADDER', - modalProps: { - accountId: account.id, - }, - }), - ); - }, - }, - null, - ); - } - - if (relationship?.followed_by) { - const handleRemoveFromFollowers = () => { - dispatch( - openModal({ - modalType: 'CONFIRM', - modalProps: { - title: intl.formatMessage(messages.confirmRemoveFromFollowersTitle), - message: intl.formatMessage( - messages.confirmRemoveFromFollowersMessage, - { name: {account.acct} }, - ), - confirm: intl.formatMessage( - messages.confirmRemoveFromFollowersButton, - ), - onConfirm: () => { - void dispatch( - removeAccountFromFollowers({ accountId: account.id }), - ); - }, - }, - }), - ); - }; - - items.push({ - text: intl.formatMessage(messages.removeFromFollowers, { - name: account.username, - }), - action: handleRemoveFromFollowers, - dangerous: true, - }); - } - - if (relationship?.muting) { - items.push({ - text: intl.formatMessage(messages.unmute, { - name: account.username, - }), - action: () => { - dispatch(unmuteAccount(account.id)); - }, - }); - } else { - items.push({ - text: intl.formatMessage(messages.mute, { - name: account.username, - }), - action: () => { - dispatch(initMuteModal(account)); - }, - dangerous: true, - }); - } - - if (relationship?.blocking) { - items.push({ - text: intl.formatMessage(messages.unblock, { - name: account.username, - }), - action: () => { - dispatch(unblockAccount(account.id)); - }, - }); - } else { - items.push({ - text: intl.formatMessage(messages.block, { - name: account.username, - }), - action: () => { - dispatch(blockAccount(account.id)); - }, - dangerous: true, - }); - } - - if (!account.suspended) { - items.push({ - text: intl.formatMessage(messages.report, { - name: account.username, - }), - action: () => { - dispatch(initReport(account)); - }, - dangerous: true, - }); - } - - const remoteDomain = isRemote ? account.acct.split('@')[1] : null; - if (remoteDomain) { - items.push(null); - - if (relationship?.domain_blocking) { - items.push({ - text: intl.formatMessage(messages.unblockDomain, { - domain: remoteDomain, - }), - action: () => { - dispatch(unblockDomain(remoteDomain)); - }, - }); - } else { - items.push({ - text: intl.formatMessage(messages.blockDomain, { - domain: remoteDomain, - }), - action: () => { - dispatch(initDomainBlockModal(account)); - }, - dangerous: true, - }); - } - } - - if ( - (permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || - (isRemote && - (permissions & PERMISSION_MANAGE_FEDERATION) === - PERMISSION_MANAGE_FEDERATION) - ) { - items.push(null); - if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { - items.push({ - text: intl.formatMessage(messages.admin_account, { - name: account.username, - }), - href: `/admin/accounts/${account.id}`, - }); - } - if ( - isRemote && - (permissions & PERMISSION_MANAGE_FEDERATION) === - PERMISSION_MANAGE_FEDERATION - ) { - items.push({ - text: intl.formatMessage(messages.admin_domain, { - domain: remoteDomain, - }), - href: `/admin/instances/${remoteDomain}`, - }); - } - } - - return items; -} - const redesignMessages = defineMessages({ share: { id: 'account.menu.share', defaultMessage: 'Share…' }, copy: { id: 'account.menu.copy', defaultMessage: 'Copy link' }, diff --git a/app/javascript/mastodon/features/account_timeline/components/number_fields.tsx b/app/javascript/mastodon/features/account_timeline/components/number_fields.tsx index 0fa805ac6ba..385ba2d28ab 100644 --- a/app/javascript/mastodon/features/account_timeline/components/number_fields.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/number_fields.tsx @@ -3,13 +3,6 @@ import type { FC } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; -import { NavLink } from 'react-router-dom'; - -import { - FollowersCounter, - FollowingCounter, - StatusesCounter, -} from '@/mastodon/components/counters'; import { FormattedDateWrapper } from '@/mastodon/components/formatted_date'; import { NumberFields, @@ -18,54 +11,9 @@ import { import { ShortNumber } from '@/mastodon/components/short_number'; import { useAccount } from '@/mastodon/hooks/useAccount'; -import { isRedesignEnabled } from '../common'; - -const LegacyNumberFields: FC<{ accountId: string }> = ({ accountId }) => { - const intl = useIntl(); - const account = useAccount(accountId); - - if (!account) { - return null; - } - - return ( -
- - - - - - - - - - - -
- ); -}; - -const RedesignNumberFields: FC<{ accountId: string }> = ({ accountId }) => { +export const AccountNumberFields: FC<{ accountId: string }> = ({ + accountId, +}) => { const intl = useIntl(); const account = useAccount(accountId); const createdThisYear = useMemo( @@ -125,7 +73,3 @@ const RedesignNumberFields: FC<{ accountId: string }> = ({ accountId }) => { ); }; - -export const AccountNumberFields = isRedesignEnabled() - ? RedesignNumberFields - : LegacyNumberFields; diff --git a/app/javascript/mastodon/features/account_timeline/components/tabs.tsx b/app/javascript/mastodon/features/account_timeline/components/tabs.tsx index 5febb8eaf8c..021005d7449 100644 --- a/app/javascript/mastodon/features/account_timeline/components/tabs.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/tabs.tsx @@ -8,40 +8,13 @@ import { NavLink } from 'react-router-dom'; import { useAccount } from '@/mastodon/hooks/useAccount'; import { useAccountId } from '@/mastodon/hooks/useAccountId'; -import { isRedesignEnabled } from '../common'; - import classes from './redesign.module.scss'; -export const AccountTabs: FC<{ acct: string }> = ({ acct }) => { - if (isRedesignEnabled()) { - return ; - } - return ( -
- - - - - - - - - - - - -
- ); -}; - const isActive: Required['isActive'] = (match, location) => match?.url === location.pathname || (!!match?.url && location.pathname.startsWith(`${match.url}/tagged/`)); -const RedesignTabs: FC = () => { +export const AccountTabs: FC = () => { const accountId = useAccountId(); const account = useAccount(accountId); diff --git a/app/javascript/mastodon/features/account_timeline/v2/filters.tsx b/app/javascript/mastodon/features/account_timeline/v2/filters.tsx index 28dcb5f5c47..2dc8254a201 100644 --- a/app/javascript/mastodon/features/account_timeline/v2/filters.tsx +++ b/app/javascript/mastodon/features/account_timeline/v2/filters.tsx @@ -23,7 +23,7 @@ export const AccountFilters: FC = () => { } return ( <> - +
diff --git a/app/javascript/mastodon/features/account_timeline/v2/pinned_statuses.tsx b/app/javascript/mastodon/features/account_timeline/v2/pinned_statuses.tsx index 694c107f3af..88503389c96 100644 --- a/app/javascript/mastodon/features/account_timeline/v2/pinned_statuses.tsx +++ b/app/javascript/mastodon/features/account_timeline/v2/pinned_statuses.tsx @@ -18,7 +18,6 @@ import type { StatusHeaderRenderFn } from '@/mastodon/components/status/header'; import { selectTimelineByKey } from '@/mastodon/selectors/timelines'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; -import { isRedesignEnabled } from '../common'; import { PinnedBadge } from '../components/badges'; import { useAccountContext } from './context'; @@ -88,10 +87,6 @@ export const renderPinnedStatusHeader: StatusHeaderRenderFn = ({ export const PinnedShowAllButton: FC = () => { const { onShowAllPinned } = useAccountContext(); - if (!isRedesignEnabled()) { - return null; - } - return (